...

Source file src/k8s.io/kubernetes/test/integration/endpointslice/endpointsliceterminating_test.go

Documentation: k8s.io/kubernetes/test/integration/endpointslice

     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  }
   285  

View as plain text