8000 test/intergration/endpointslice: add tests for endpointslice terminat… · kubernetes/kubernetes@fd0db61 · GitHub
[go: up one dir, main page]

Skip to content

Commit fd0db61

Browse files
committed
test/intergration/endpointslice: add tests for endpointslice terminating condition
Signed-off-by: Andrew Sy Kim <kim.andrewsy@gmail.com>
1 parent 826a521 commit fd0db61

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package endpointslice
18+
19+
import (
20+
"context"
21+
"reflect"
22+
"testing"
23+
"time"
24+
25+
corev1 "k8s.io/api/core/v1"
26+
discovery "k8s.io/api/discovery/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/util/intstr"
29+
"k8s.io/apimachinery/pkg/util/wait"
30+
utilfeature "k8s.io/apiserver/pkg/util/feature"
31+
"k8s.io/client-go/informers"
32+
clientset "k8s.io/client-go/kubernetes"
33+
restclient "k8s.io/client-go/rest"
34+
featuregatetesting "k8s.io/component-base/featuregate/testing"
35+
"k8s.io/kubernetes/pkg/controller/endpointslice"
36+
"k8s.io/kubernetes/pkg/features"
37+
"k8s.io/kubernetes/test/integration/framework"
38+
utilpointer "k8s.io/utils/pointer"
39+
)
40+
41+
// TestEndpointSliceTerminating tests that terminating pods are NOT included in EndpointSlice when
42+
// the feature gate EndpointSliceTerminatingCondition is off. If the gate is on, it tests that
43+
// terminating endpoints are included but with the correct conditions set for ready, serving and terminating.
44+
func TestEndpointSliceTerminating(t *testing.T) {
45+
testcases := []struct {
46+
name string
47+
podStatus corev1.PodStatus
48+
expectedEndpoints []discovery.Endpoint
49+
terminatingGate bool
50+
}{
51+
{
52+
name: "ready terminating pods not included, terminating gate off",
53+
podStatus: corev1.PodStatus{
54+
Phase: corev1.PodRunning,
55+
Conditions: []corev1.PodCondition{
56+
{
57+
Type: corev1.PodReady,
58+
Status: corev1.ConditionTrue,
59+
},
60+
},
61+
PodIP: "10.0.0.1",
62+
PodIPs: []corev1.PodIP{
63+
{
64+
IP: "10.0.0.1",
65+
},
66+
},
67+
},
68+
expectedEndpoints: []discovery.Endpoint{},
69+
terminatingGate: false,
70+
},
71+
{
72+
name: "not ready terminating pods not included, terminating gate off",
73+
podStatus: corev1.PodStatus{
74+
Phase: corev1.PodRunning,
75+
Conditions: []corev1.PodCondition{
76+
{
77+
Type: corev1.PodReady,
78+
Status: corev1.ConditionFalse,
79+
},
80+
},
81+
PodIP: "10.0.0.1",
82+
PodIPs: []corev1.PodIP{
83+
{
84+
IP: "10.0.0.1",
85+
},
86+
},
87+
},
88+
expectedEndpoints: []discovery.Endpoint{},
89+
terminatingGate: false,
90+
},
91+
{
92+
name: "ready terminating pods included, terminating gate on",
93+
podStatus: corev1.PodStatus{
94+
Phase: corev1.PodRunning,
95+
Conditions: []corev1.PodCondition{
96+
{
97+
Type: corev1.PodReady,
98+
Status: corev1.ConditionTrue,
99+
},
100+
},
101+
PodIP: "10.0.0.1",
102+
PodIPs: []corev1.PodIP{
103+
{
104+
IP: "10.0.0.1",
105+
},
106+
},
107+
},
108+
expectedEndpoints: []discovery.Endpoint{
109+
{
110+
Addresses: []string{"10.0.0.1"},
111+
Conditions: discovery.EndpointConditions{
112+
Ready: utilpointer.BoolPtr(false),
113+
Serving: utilpointer.BoolPtr(true),
114+
Terminating: utilpointer.BoolPtr(true),
115+
},
116+
},
117+
},
118+
terminatingGate: true,
119+
},
120+
{
121+
name: "not ready terminating pods included, terminating gate on",
122+
podStatus: corev1.PodStatus{
123+
Phase: corev1.PodRunning,
124+
Conditions: []corev1.PodCondition{
125+
{
126+
Type: corev1.PodReady,
127+
Status: corev1.ConditionFalse,
128+
},
129+
},
130+
PodIP: "10.0.0.1",
131+
PodIPs: []corev1.PodIP{
132+
{
133+
IP: "10.0.0.1",
134+
},
135+
},
136+
},
137+
expectedEndpoints: []discovery.Endpoint{
138+
{
139+
Addresses: []string{"10.0.0.1"},
140+
Conditions: discovery.EndpointConditions{
141+
Ready: utilpointer.BoolPtr(false),
142+
Serving: utilpointer.BoolPtr(false),
143+
Terminating: utilpointer.BoolPtr(true),
144+
},
145+
},
146+
},
147+
terminatingGate: true,
148+
},
149+
}
150+
151+
for _, testcase := range testcases {
152+
t.Run(testcase.name, func(t *testing.T) {
153+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EndpointSliceTerminatingCondition, testcase.terminatingGate)()
154+
155+
controlPlaneConfig := framework.NewIntegrationTestControlPlaneConfig()
156+
_, server, closeFn := framework.RunAnAPIServer(controlPlaneConfig)
157+
defer closeFn()
158+
159+
config := restclient.Config{Host: server.URL}
160+
client, err := clientset.NewForConfig(&config)
161+
if err != nil {
162+
t.Fatalf("Error creating clientset: %v", err)
163+
}
164+
165+
resyncPeriod := 12 * time.Hour
166+
informers := informers.NewSharedInformerFactory(client, resyncPeriod)
167+
168+
epsController := endpointslice.NewController(
169+
informers.Core().V1().Pods(),
170+
informers.Core().V1().Services(),
171+
informers.Core().V1().Nodes(),
172+
informers.Discovery().V1().EndpointSlices(),
173+
int32(100),
174+
client,
175+
1*time.Second)
176+
177+
// Start informer and controllers
178+
stopCh := make(chan struct{})
179+
defer close(stopCh)
180+
informers.Start(stopCh)
181+
go epsController.Run(1, stopCh)
182+
183+
// Create namespace
184+
ns := framework.CreateTestingNamespace("test-endpoints-terminating", server, t)
185+
defer framework.DeleteTestingNamespace(ns, server, t)
186+
187+
node := &corev1.Node{
188+
ObjectMeta: metav1.ObjectMeta{
189+
Name: "fake-node",
190+
},
191+
}
192+
193+
_, err = client.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
194+
if err != nil {
195+
t.Fatalf("Failed to create test node: %v", err)
196+
}
197+
198+
svc := &corev1.Service{
199+
ObjectMeta: metav1.ObjectMeta{
200+
Name: "test-service",
201+
Namespace: ns.Name,
202+
Labels: map[string]string{
203+
"foo": "bar",
204+
},
205+
},
206+
Spec: corev1.ServiceSpec{
207+
Selector: map[string]string{
208+
"foo": "bar",
209+
},
210+
Ports: []corev1.ServicePort{
211+
{Name: "port-443", Port: 443, Protocol: "TCP", TargetPort: intstr.FromInt(443)},
212+
},
213+
},
214+
}
215+
216+
_, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), svc, metav1.CreateOptions{})
217+
if err != nil {
218+
t.Fatalf("Failed to create test Service: %v", err)
219+
}
220+
221+
pod := &corev1.Pod{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Name: "test-pod",
224+
Labels: map[string]string{
225+
"foo": "bar",
226+
},
227+
},
228+
Spec: corev1.PodSpec{
229+
NodeName: "fake-node",
230+
Containers: []corev1.Container{
231+
{
232+
Name: "fakename",
233+
Image: "fakeimage",
234+
Ports: []corev1.ContainerPort{
235+
{
236+
Name: "port-443",
237+
ContainerPort: 443,
238+
},
239+
},
240+
},
241+
},
242+
},
243+
}
244+
245+
pod, err = client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
246+
if err != nil {
247+
t.Fatalf("Failed to create test ready pod: %v", err)
248+
}
249+
250+
pod.Status = testcase.podStatus
251+
_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{})
252+
if err != nil {
253+
t.Fatalf("Failed to update status for test ready pod: %v", err)
254+
}
255+
256+
// first check that endpoints are included, test should always have 1 initial endpoint
257+
err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
258+
esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{
259+
LabelSelector: discovery.LabelServiceName + "=" + svc.Name,
260+
})
261+
262+
if err != nil {
263+
return false, err
264+
}
265+
266+
if len(esList.Items) == 0 {
267+
return false, nil
268+
}
269+
270+
numEndpoints := 0
271+
for _, slice := range esList.Items {
272+
numEndpoints += len(slice.Endpoints)
273+
}
274+
275+
if numEndpoints > 0 {
276+
return true, nil
277+
}
278+
279+
return false, nil
280+
})
281+
if err != nil {
282+
t.Errorf("Error waiting for endpoint slices: %v", err)
283+
}
284+
285+
// Delete pod and check endpoints slice conditions
286+
err = client.CoreV1().Pods(ns.Name).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{})
287+
if err != nil {
288+
t.Fatalf("Failed to delete pod in terminating state: %v", err)
289+
}
290+
291+
// Validate that terminating the endpoint will result in the expected endpoints in EndpointSlice.
292+
// Use a stricter timeout value here since we should try to catch regressions in the time it takes to remove terminated endpoints.
293+
var endpoints []discovery.Endpoint
294+
err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
295+
esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{
296+
LabelSelector: discovery.LabelServiceName + "=" + svc.Name,
297+
})
298+
299+
if err != nil {
300+
return false, err
301+
}
302+
303+
if len(esList.Items) == 0 {
304+
return false, nil
305+
}
306+
307+
endpoints = esList.Items[0].Endpoints
308+
if len(endpoints) == 0 && len(testcase.expectedEndpoints) == 0 {
309+
return true, nil
310+
}
311+
312+
if len(endpoints) != len(testcase.expectedEndpoints) {
313+
return false, nil
314+
}
315+
316+
if !reflect.DeepEqual(endpoints[0].Addresses, testcase.expectedEndpoints[0].Addresses) {
317+
return false, nil
318+
}
319+
320+
if !reflect.DeepEqual(endpoints[0].Conditions, testcase.expectedEndpoints[0].Conditions) {
321+
return false, nil
322+
}
323+
324+
return true, nil
325+
})
326+
if err != nil {
327+
t.Logf("actual endpoints: %v", endpoints)
328+
t.Logf("expected endpoints: %v", testcase.expectedEndpoints)
329+
t.Errorf("unexpected endpoints: %v", err)
330+
}
331+
})
332+
}
333+
}

0 commit comments

Comments
 (0)
0