k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/endpointslice/endpointsliceterminating_test.go (about) 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 "k8s.io/client-go/informers" 31 clientset "k8s.io/client-go/kubernetes" 32 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 33 "k8s.io/kubernetes/pkg/controller/endpointslice" 34 "k8s.io/kubernetes/test/integration/framework" 35 "k8s.io/kubernetes/test/utils/ktesting" 36 utilpointer "k8s.io/utils/pointer" 37 ) 38 39 // TestEndpointSliceTerminating tests that terminating endpoints are included with the 40 // correct conditions set for ready, serving and terminating. 41 func TestEndpointSliceTerminating(t *testing.T) { 42 testcases := []struct { 43 name string 44 podStatus corev1.PodStatus 45 expectedEndpoints []discovery.Endpoint 46 }{ 47 { 48 name: "ready terminating pods", 49 podStatus: corev1.PodStatus{ 50 Phase: corev1.PodRunning, 51 Conditions: []corev1.PodCondition{ 52 { 53 Type: corev1.PodReady, 54 Status: corev1.ConditionTrue, 55 }, 56 }, 57 PodIP: "10.0.0.1", 58 PodIPs: []corev1.PodIP{ 59 { 60 IP: "10.0.0.1", 61 }, 62 }, 63 }, 64 expectedEndpoints: []discovery.Endpoint{ 65 { 66 Addresses: []string{"10.0.0.1"}, 67 Conditions: discovery.EndpointConditions{ 68 Ready: utilpointer.BoolPtr(false), 69 Serving: utilpointer.BoolPtr(true), 70 Terminating: utilpointer.BoolPtr(true), 71 }, 72 }, 73 }, 74 }, 75 { 76 name: "not ready terminating pods", 77 podStatus: corev1.PodStatus{ 78 Phase: corev1.PodRunning, 79 Conditions: []corev1.PodCondition{ 80 { 81 Type: corev1.PodReady, 82 Status: corev1.ConditionFalse, 83 }, 84 }, 85 PodIP: "10.0.0.1", 86 PodIPs: []corev1.PodIP{ 87 { 88 IP: "10.0.0.1", 89 }, 90 }, 91 }, 92 expectedEndpoints: []discovery.Endpoint{ 93 { 94 Addresses: []string{"10.0.0.1"}, 95 Conditions: discovery.EndpointConditions{ 96 Ready: utilpointer.BoolPtr(false), 97 Serving: utilpointer.BoolPtr(false), 98 Terminating: utilpointer.BoolPtr(true), 99 }, 100 }, 101 }, 102 }, 103 } 104 105 for _, testcase := range testcases { 106 t.Run(testcase.name, func(t *testing.T) { 107 // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. 108 server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd()) 109 defer server.TearDownFn() 110 111 client, err := clientset.NewForConfig(server.ClientConfig) 112 if err != nil { 113 t.Fatalf("Error creating clientset: %v", err) 114 } 115 116 resyncPeriod := 12 * time.Hour 117 informers := informers.NewSharedInformerFactory(client, resyncPeriod) 118 119 tCtx := ktesting.Init(t) 120 epsController := endpointslice.NewController( 121 tCtx, 122 informers.Core().V1().Pods(), 123 informers.Core().V1().Services(), 124 informers.Core().V1().Nodes(), 125 informers.Discovery().V1().EndpointSlices(), 126 int32(100), 127 client, 128 1*time.Second) 129 130 // Start informer and controllers 131 informers.Start(tCtx.Done()) 132 go epsController.Run(tCtx, 1) 133 134 // Create namespace 135 ns := framework.CreateNamespaceOrDie(client, "test-endpoints-terminating", t) 136 defer framework.DeleteNamespaceOrDie(client, ns, t) 137 138 node := &corev1.Node{ 139 ObjectMeta: metav1.ObjectMeta{ 140 Name: "fake-node", 141 }, 142 } 143 144 _, err = client.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{}) 145 if err != nil { 146 t.Fatalf("Failed to create test node: %v", err) 147 } 148 149 svc := &corev1.Service{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: "test-service", 152 Namespace: ns.Name, 153 Labels: map[string]string{ 154 "foo": "bar", 155 }, 156 }, 157 Spec: corev1.ServiceSpec{ 158 Selector: map[string]string{ 159 "foo": "bar", 160 }, 161 Ports: []corev1.ServicePort{ 162 {Name: "port-443", Port: 443, Protocol: "TCP", TargetPort: intstr.FromInt32(443)}, 163 }, 164 }, 165 } 166 167 _, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), svc, metav1.CreateOptions{}) 168 if err != nil { 169 t.Fatalf("Failed to create test Service: %v", err) 170 } 171 172 pod := &corev1.Pod{ 173 ObjectMeta: metav1.ObjectMeta{ 174 Name: "test-pod", 175 Labels: map[string]string{ 176 "foo": "bar", 177 }, 178 }, 179 Spec: corev1.PodSpec{ 180 NodeName: "fake-node", 181 Containers: []corev1.Container{ 182 { 183 Name: "fakename", 184 Image: "fakeimage", 185 Ports: []corev1.ContainerPort{ 186 { 187 Name: "port-443", 188 ContainerPort: 443, 189 }, 190 }, 191 }, 192 }, 193 }, 194 } 195 196 pod, err = client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}) 197 if err != nil { 198 t.Fatalf("Failed to create test ready pod: %v", err) 199 } 200 201 pod.Status = testcase.podStatus 202 _, err = client.CoreV1().Pods(ns.Name).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}) 203 if err != nil { 204 t.Fatalf("Failed to update status for test ready pod: %v", err) 205 } 206 207 // first check that endpoints are included, test should always have 1 initial endpoint 208 err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) { 209 esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{ 210 LabelSelector: discovery.LabelServiceName + "=" + svc.Name, 211 }) 212 213 if err != nil { 214 return false, err 215 } 216 217 if len(esList.Items) == 0 { 218 return false, nil 219 } 220 221 numEndpoints := 0 222 for _, slice := range esList.Items { 223 numEndpoints += len(slice.Endpoints) 224 } 225 226 if numEndpoints > 0 { 227 return true, nil 228 } 229 230 return false, nil 231 }) 232 if err != nil { 233 t.Errorf("Error waiting for endpoint slices: %v", err) 234 } 235 236 // Delete pod and check endpoints slice conditions 237 err = client.CoreV1().Pods(ns.Name).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}) 238 if err != nil { 239 t.Fatalf("Failed to delete pod in terminating state: %v", err) 240 } 241 242 // Validate that terminating the endpoint will result in the expected endpoints in EndpointSlice. 243 // Use a stricter timeout value here since we should try to catch regressions in the time it takes to remove terminated endpoints. 244 var endpoints []discovery.Endpoint 245 err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) { 246 esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{ 247 LabelSelector: discovery.LabelServiceName + "=" + svc.Name, 248 }) 249 250 if err != nil { 251 return false, err 252 } 253 254 if len(esList.Items) == 0 { 255 return false, nil 256 } 257 258 endpoints = esList.Items[0].Endpoints 259 if len(endpoints) == 0 && len(testcase.expectedEndpoints) == 0 { 260 return true, nil 261 } 262 263 if len(endpoints) != len(testcase.expectedEndpoints) { 264 return false, nil 265 } 266 267 if !reflect.DeepEqual(endpoints[0].Addresses, testcase.expectedEndpoints[0].Addresses) { 268 return false, nil 269 } 270 271 if !reflect.DeepEqual(endpoints[0].Conditions, testcase.expectedEndpoints[0].Conditions) { 272 return false, nil 273 } 274 275 return true, nil 276 }) 277 if err != nil { 278 t.Logf("actual endpoints: %v", endpoints) 279 t.Logf("expected endpoints: %v", testcase.expectedEndpoints) 280 t.Errorf("unexpected endpoints: %v", err) 281 } 282 }) 283 } 284 }