k8s.io/kubernetes@v1.29.3/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  	"k8s.io/klog/v2/ktesting"
    33  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    34  	"k8s.io/kubernetes/pkg/controller/endpointslice"
    35  	"k8s.io/kubernetes/test/integration/framework"
    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  			_, ctx := ktesting.NewTestContext(t)
   120  			epsController := endpointslice.NewController(
   121  				ctx,
   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  			ctx, cancel := context.WithCancel(ctx)
   132  			defer cancel()
   133  			informers.Start(ctx.Done())
   134  			go epsController.Run(ctx, 1)
   135  
   136  			// Create namespace
   137  			ns := framework.CreateNamespaceOrDie(client, "test-endpoints-terminating", t)
   138  			defer framework.DeleteNamespaceOrDie(client, ns, t)
   139  
   140  			node := &corev1.Node{
   141  				ObjectMeta: metav1.ObjectMeta{
   142  					Name: "fake-node",
   143  				},
   144  			}
   145  
   146  			_, err = client.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
   147  			if err != nil {
   148  				t.Fatalf("Failed to create test node: %v", err)
   149  			}
   150  
   151  			svc := &corev1.Service{
   152  				ObjectMeta: metav1.ObjectMeta{
   153  					Name:      "test-service",
   154  					Namespace: ns.Name,
   155  					Labels: map[string]string{
   156  						"foo": "bar",
   157  					},
   158  				},
   159  				Spec: corev1.ServiceSpec{
   160  					Selector: map[string]string{
   161  						"foo": "bar",
   162  					},
   163  					Ports: []corev1.ServicePort{
   164  						{Name: "port-443", Port: 443, Protocol: "TCP", TargetPort: intstr.FromInt32(443)},
   165  					},
   166  				},
   167  			}
   168  
   169  			_, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), svc, metav1.CreateOptions{})
   170  			if err != nil {
   171  				t.Fatalf("Failed to create test Service: %v", err)
   172  			}
   173  
   174  			pod := &corev1.Pod{
   175  				ObjectMeta: metav1.ObjectMeta{
   176  					Name: "test-pod",
   177  					Labels: map[string]string{
   178  						"foo": "bar",
   179  					},
   180  				},
   181  				Spec: corev1.PodSpec{
   182  					NodeName: "fake-node",
   183  					Containers: []corev1.Container{
   184  						{
   185  							Name:  "fakename",
   186  							Image: "fakeimage",
   187  							Ports: []corev1.ContainerPort{
   188  								{
   189  									Name:          "port-443",
   190  									ContainerPort: 443,
   191  								},
   192  							},
   193  						},
   194  					},
   195  				},
   196  			}
   197  
   198  			pod, err = client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{})
   199  			if err != nil {
   200  				t.Fatalf("Failed to create test ready pod: %v", err)
   201  			}
   202  
   203  			pod.Status = testcase.podStatus
   204  			_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{})
   205  			if err != nil {
   206  				t.Fatalf("Failed to update status for test ready pod: %v", err)
   207  			}
   208  
   209  			// first check that endpoints are included, test should always have 1 initial endpoint
   210  			err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
   211  				esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{
   212  					LabelSelector: discovery.LabelServiceName + "=" + svc.Name,
   213  				})
   214  
   215  				if err != nil {
   216  					return false, err
   217  				}
   218  
   219  				if len(esList.Items) == 0 {
   220  					return false, nil
   221  				}
   222  
   223  				numEndpoints := 0
   224  				for _, slice := range esList.Items {
   225  					numEndpoints += len(slice.Endpoints)
   226  				}
   227  
   228  				if numEndpoints > 0 {
   229  					return true, nil
   230  				}
   231  
   232  				return false, nil
   233  			})
   234  			if err != nil {
   235  				t.Errorf("Error waiting for endpoint slices: %v", err)
   236  			}
   237  
   238  			// Delete pod and check endpoints slice conditions
   239  			err = client.CoreV1().Pods(ns.Name).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{})
   240  			if err != nil {
   241  				t.Fatalf("Failed to delete pod in terminating state: %v", err)
   242  			}
   243  
   244  			// Validate that terminating the endpoint will result in the expected endpoints in EndpointSlice.
   245  			// Use a stricter timeout value here since we should try to catch regressions in the time it takes to remove terminated endpoints.
   246  			var endpoints []discovery.Endpoint
   247  			err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
   248  				esList, err := client.DiscoveryV1().EndpointSlices(ns.Name).List(context.TODO(), metav1.ListOptions{
   249  					LabelSelector: discovery.LabelServiceName + "=" + svc.Name,
   250  				})
   251  
   252  				if err != nil {
   253  					return false, err
   254  				}
   255  
   256  				if len(esList.Items) == 0 {
   257  					return false, nil
   258  				}
   259  
   260  				endpoints = esList.Items[0].Endpoints
   261  				if len(endpoints) == 0 && len(testcase.expectedEndpoints) == 0 {
   262  					return true, nil
   263  				}
   264  
   265  				if len(endpoints) != len(testcase.expectedEndpoints) {
   266  					return false, nil
   267  				}
   268  
   269  				if !reflect.DeepEqual(endpoints[0].Addresses, testcase.expectedEndpoints[0].Addresses) {
   270  					return false, nil
   271  				}
   272  
   273  				if !reflect.DeepEqual(endpoints[0].Conditions, testcase.expectedEndpoints[0].Conditions) {
   274  					return false, nil
   275  				}
   276  
   277  				return true, nil
   278  			})
   279  			if err != nil {
   280  				t.Logf("actual endpoints: %v", endpoints)
   281  				t.Logf("expected endpoints: %v", testcase.expectedEndpoints)
   282  				t.Errorf("unexpected endpoints: %v", err)
   283  			}
   284  		})
   285  	}
   286  }