k8s.io/kubernetes@v1.29.3/test/integration/endpoints/endpoints_test.go (about)

     1  /*
     2  Copyright 2020 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 endpoints
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"testing"
    24  	"time"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/intstr"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	"k8s.io/client-go/informers"
    32  	clientset "k8s.io/client-go/kubernetes"
    33  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    34  	"k8s.io/kubernetes/pkg/controller/endpoint"
    35  	"k8s.io/kubernetes/test/integration/framework"
    36  )
    37  
    38  func TestEndpointUpdates(t *testing.T) {
    39  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
    40  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
    41  	defer server.TearDownFn()
    42  
    43  	client, err := clientset.NewForConfig(server.ClientConfig)
    44  	if err != nil {
    45  		t.Fatalf("Error creating clientset: %v", err)
    46  	}
    47  
    48  	informers := informers.NewSharedInformerFactory(client, 0)
    49  
    50  	epController := endpoint.NewEndpointController(
    51  		informers.Core().V1().Pods(),
    52  		informers.Core().V1().Services(),
    53  		informers.Core().V1().Endpoints(),
    54  		client,
    55  		0)
    56  
    57  	// Start informer and controllers
    58  	ctx, cancel := context.WithCancel(context.Background())
    59  	defer cancel()
    60  	informers.Start(ctx.Done())
    61  	go epController.Run(ctx, 1)
    62  
    63  	// Create namespace
    64  	ns := framework.CreateNamespaceOrDie(client, "test-endpoints-updates", t)
    65  	defer framework.DeleteNamespaceOrDie(client, ns, t)
    66  
    67  	// Create a pod with labels
    68  	pod := &v1.Pod{
    69  		ObjectMeta: metav1.ObjectMeta{
    70  			Name:      "test-pod",
    71  			Namespace: ns.Name,
    72  			Labels:    labelMap(),
    73  		},
    74  		Spec: v1.PodSpec{
    75  			NodeName: "fakenode",
    76  			Containers: []v1.Container{
    77  				{
    78  					Name:  "fake-name",
    79  					Image: "fakeimage",
    80  				},
    81  			},
    82  		},
    83  	}
    84  
    85  	createdPod, err := client.CoreV1().Pods(ns.Name).Create(ctx, pod, metav1.CreateOptions{})
    86  	if err != nil {
    87  		t.Fatalf("Failed to create pod %s: %v", pod.Name, err)
    88  	}
    89  
    90  	// Set pod IPs
    91  	createdPod.Status = v1.PodStatus{
    92  		Phase:  v1.PodRunning,
    93  		PodIPs: []v1.PodIP{{IP: "1.1.1.1"}, {IP: "2001:db8::"}},
    94  	}
    95  	_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(ctx, createdPod, metav1.UpdateOptions{})
    96  	if err != nil {
    97  		t.Fatalf("Failed to update status of pod %s: %v", pod.Name, err)
    98  	}
    99  
   100  	// Create a service associated to the pod
   101  	svc := newService(ns.Name, "foo1")
   102  	svc1, err := client.CoreV1().Services(ns.Name).Create(ctx, svc, metav1.CreateOptions{})
   103  	if err != nil {
   104  		t.Fatalf("Failed to create service %s: %v", svc.Name, err)
   105  	}
   106  
   107  	// Obtain ResourceVersion of the new endpoint created
   108  	var resVersion string
   109  	if err := wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) {
   110  		endpoints, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc.Name, metav1.GetOptions{})
   111  		if err != nil {
   112  			t.Logf("error fetching endpoints: %v", err)
   113  			return false, nil
   114  		}
   115  		resVersion = endpoints.ObjectMeta.ResourceVersion
   116  		return true, nil
   117  	}); err != nil {
   118  		t.Fatalf("endpoints not found: %v", err)
   119  	}
   120  
   121  	// Force recomputation on the endpoint controller
   122  	svc1.SetAnnotations(map[string]string{"foo": "bar"})
   123  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc1, metav1.UpdateOptions{})
   124  	if err != nil {
   125  		t.Fatalf("Failed to update service %s: %v", svc1.Name, err)
   126  	}
   127  
   128  	// Create a new service and wait until it has been processed,
   129  	// this way we can be sure that the endpoint for the original service
   130  	// was recomputed before asserting, since we only have 1 worker
   131  	// in the endpoint controller
   132  	svc2 := newService(ns.Name, "foo2")
   133  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, svc2, metav1.CreateOptions{})
   134  	if err != nil {
   135  		t.Fatalf("Failed to create service %s: %v", svc.Name, err)
   136  	}
   137  
   138  	if err := wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) {
   139  		_, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc2.Name, metav1.GetOptions{})
   140  		if err != nil {
   141  			t.Logf("error fetching endpoints: %v", err)
   142  			return false, nil
   143  		}
   144  		return true, nil
   145  	}); err != nil {
   146  		t.Fatalf("endpoints not found: %v", err)
   147  	}
   148  
   149  	// the endpoint controller should not update the endpoint created for the original
   150  	// service since nothing has changed, the resource version has to be the same
   151  	endpoints, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc.Name, metav1.GetOptions{})
   152  	if err != nil {
   153  		t.Fatalf("error fetching endpoints: %v", err)
   154  	}
   155  	if resVersion != endpoints.ObjectMeta.ResourceVersion {
   156  		t.Fatalf("endpoints resource version does not match, expected %s received %s", resVersion, endpoints.ObjectMeta.ResourceVersion)
   157  	}
   158  
   159  }
   160  
   161  // TestExternalNameToClusterIPTransition tests that Service of type ExternalName
   162  // does not get endpoints, and after transition to ClusterIP, service gets endpoint,
   163  // without headless label
   164  func TestExternalNameToClusterIPTransition(t *testing.T) {
   165  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   166  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   167  	defer server.TearDownFn()
   168  
   169  	client, err := clientset.NewForConfig(server.ClientConfig)
   170  	if err != nil {
   171  		t.Fatalf("Error creating clientset: %v", err)
   172  	}
   173  
   174  	informers := informers.NewSharedInformerFactory(client, 0)
   175  
   176  	epController := endpoint.NewEndpointController(
   177  		informers.Core().V1().Pods(),
   178  		informers.Core().V1().Services(),
   179  		informers.Core().V1().Endpoints(),
   180  		client,
   181  		0)
   182  
   183  	// Start informer and controllers
   184  	ctx, cancel := context.WithCancel(context.Background())
   185  	defer cancel()
   186  	informers.Start(ctx.Done())
   187  	go epController.Run(ctx, 1)
   188  
   189  	// Create namespace
   190  	ns := framework.CreateNamespaceOrDie(client, "test-endpoints-updates", t)
   191  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   192  
   193  	// Create a pod with labels
   194  	pod := &v1.Pod{
   195  		ObjectMeta: metav1.ObjectMeta{
   196  			Name:      "test-pod",
   197  			Namespace: ns.Name,
   198  			Labels:    labelMap(),
   199  		},
   200  		Spec: v1.PodSpec{
   201  			NodeName: "fakenode",
   202  			Containers: []v1.Container{
   203  				{
   204  					Name:  "fake-name",
   205  					Image: "fakeimage",
   206  				},
   207  			},
   208  		},
   209  	}
   210  
   211  	createdPod, err := client.CoreV1().Pods(ns.Name).Create(ctx, pod, metav1.CreateOptions{})
   212  	if err != nil {
   213  		t.Fatalf("Failed to create pod %s: %v", pod.Name, err)
   214  	}
   215  
   216  	// Set pod IPs
   217  	createdPod.Status = v1.PodStatus{
   218  		Phase:  v1.PodRunning,
   219  		PodIPs: []v1.PodIP{{IP: "1.1.1.1"}, {IP: "2001:db8::"}},
   220  	}
   221  	_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(ctx, createdPod, metav1.UpdateOptions{})
   222  	if err != nil {
   223  		t.Fatalf("Failed to update status of pod %s: %v", pod.Name, err)
   224  	}
   225  
   226  	// Create an ExternalName service associated to the pod
   227  	svc := newExternalNameService(ns.Name, "foo1")
   228  	svc1, err := client.CoreV1().Services(ns.Name).Create(ctx, svc, metav1.CreateOptions{})
   229  	if err != nil {
   230  		t.Fatalf("Failed to create service %s: %v", svc.Name, err)
   231  	}
   232  
   233  	err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
   234  		endpoints, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc.Name, metav1.GetOptions{})
   235  		if err == nil {
   236  			t.Errorf("expected no endpoints for externalName service, got: %v", endpoints)
   237  			return true, nil
   238  		}
   239  		return false, nil
   240  	})
   241  	if err == nil {
   242  		t.Errorf("expected error waiting for endpoints")
   243  	}
   244  
   245  	// update service to ClusterIP type and verify endpoint was created
   246  	svc1.Spec.Type = v1.ServiceTypeClusterIP
   247  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc1, metav1.UpdateOptions{})
   248  	if err != nil {
   249  		t.Fatalf("Failed to update service %s: %v", svc1.Name, err)
   250  	}
   251  
   252  	if err := wait.PollImmediate(1*time.Second, wait.ForeverTestTimeout, func() (bool, error) {
   253  		ep, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc1.Name, metav1.GetOptions{})
   254  		if err != nil {
   255  			t.Logf("no endpoints found, error: %v", err)
   256  			return false, nil
   257  		}
   258  		t.Logf("endpoint %s was successfully created", svc1.Name)
   259  		if _, ok := ep.Labels[v1.IsHeadlessService]; ok {
   260  			t.Errorf("ClusterIP endpoint should not have headless label, got: %v", ep)
   261  		}
   262  		return true, nil
   263  	}); err != nil {
   264  		t.Fatalf("endpoints not found: %v", err)
   265  	}
   266  }
   267  
   268  // TestEndpointWithTerminatingPod tests that terminating pods are NOT included in Endpoints.
   269  // This capability is only available in the newer EndpointSlice API and there are no plans to
   270  // include it for Endpoints. This test can be removed in the future if we decide to include
   271  // terminating endpoints in Endpoints, but in the mean time this test ensures we do not change
   272  // this behavior accidentally.
   273  func TestEndpointWithTerminatingPod(t *testing.T) {
   274  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   275  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   276  	defer server.TearDownFn()
   277  
   278  	client, err := clientset.NewForConfig(server.ClientConfig)
   279  	if err != nil {
   280  		t.Fatalf("Error creating clientset: %v", err)
   281  	}
   282  
   283  	informers := informers.NewSharedInformerFactory(client, 0)
   284  
   285  	epController := endpoint.NewEndpointController(
   286  		informers.Core().V1().Pods(),
   287  		informers.Core().V1().Services(),
   288  		informers.Core().V1().Endpoints(),
   289  		client,
   290  		0)
   291  
   292  	// Start informer and controllers
   293  	ctx, cancel := context.WithCancel(context.Background())
   294  	defer cancel()
   295  	informers.Start(ctx.Done())
   296  	go epController.Run(ctx, 1)
   297  
   298  	// Create namespace
   299  	ns := framework.CreateNamespaceOrDie(client, "test-endpoints-terminating", t)
   300  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   301  
   302  	// Create a pod with labels
   303  	pod := &v1.Pod{
   304  		ObjectMeta: metav1.ObjectMeta{
   305  			Name:   "test-pod",
   306  			Labels: labelMap(),
   307  		},
   308  		Spec: v1.PodSpec{
   309  			NodeName: "fake-node",
   310  			Containers: []v1.Container{
   311  				{
   312  					Name:  "fakename",
   313  					Image: "fakeimage",
   314  					Ports: []v1.ContainerPort{
   315  						{
   316  							Name:          "port-443",
   317  							ContainerPort: 443,
   318  						},
   319  					},
   320  				},
   321  			},
   322  		},
   323  		Status: v1.PodStatus{
   324  			Phase: v1.PodRunning,
   325  			Conditions: []v1.PodCondition{
   326  				{
   327  					Type:   v1.PodReady,
   328  					Status: v1.ConditionTrue,
   329  				},
   330  			},
   331  			PodIP: "10.0.0.1",
   332  			PodIPs: []v1.PodIP{
   333  				{
   334  					IP: "10.0.0.1",
   335  				},
   336  			},
   337  		},
   338  	}
   339  
   340  	createdPod, err := client.CoreV1().Pods(ns.Name).Create(ctx, pod, metav1.CreateOptions{})
   341  	if err != nil {
   342  		t.Fatalf("Failed to create pod %s: %v", pod.Name, err)
   343  	}
   344  
   345  	createdPod.Status = pod.Status
   346  	_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(ctx, createdPod, metav1.UpdateOptions{})
   347  	if err != nil {
   348  		t.Fatalf("Failed to update status of pod %s: %v", pod.Name, err)
   349  	}
   350  
   351  	// Create a service associated to the pod
   352  	svc := &v1.Service{
   353  		ObjectMeta: metav1.ObjectMeta{
   354  			Name:      "test-service",
   355  			Namespace: ns.Name,
   356  			Labels: map[string]string{
   357  				"foo": "bar",
   358  			},
   359  		},
   360  		Spec: v1.ServiceSpec{
   361  			Selector: map[string]string{
   362  				"foo": "bar",
   363  			},
   364  			Ports: []v1.ServicePort{
   365  				{Name: "port-443", Port: 443, Protocol: "TCP", TargetPort: intstr.FromInt32(443)},
   366  			},
   367  		},
   368  	}
   369  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, svc, metav1.CreateOptions{})
   370  	if err != nil {
   371  		t.Fatalf("Failed to create service %s: %v", svc.Name, err)
   372  	}
   373  
   374  	// poll until associated Endpoints to the previously created Service exists
   375  	if err := wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
   376  		endpoints, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc.Name, metav1.GetOptions{})
   377  		if err != nil {
   378  			return false, nil
   379  		}
   380  
   381  		numEndpoints := 0
   382  		for _, subset := range endpoints.Subsets {
   383  			numEndpoints += len(subset.Addresses)
   384  		}
   385  
   386  		if numEndpoints == 0 {
   387  			return false, nil
   388  		}
   389  
   390  		return true, nil
   391  	}); err != nil {
   392  		t.Fatalf("endpoints not found: %v", err)
   393  	}
   394  
   395  	err = client.CoreV1().Pods(ns.Name).Delete(ctx, pod.Name, metav1.DeleteOptions{})
   396  	if err != nil {
   397  		t.Fatalf("error deleting test pod: %v", err)
   398  	}
   399  
   400  	// poll until endpoint for deleted Pod is no longer in Endpoints.
   401  	if err := wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
   402  		// Ensure that the recently deleted Pod exists but with a deletion timestamp. If the Pod does not exist,
   403  		// we should fail the test since it is no longer validating against a terminating pod.
   404  		pod, err := client.CoreV1().Pods(ns.Name).Get(ctx, pod.Name, metav1.GetOptions{})
   405  		if apierrors.IsNotFound(err) {
   406  			return false, fmt.Errorf("expected Pod %q to exist with deletion timestamp but was not found: %v", pod.Name, err)
   407  		}
   408  		if err != nil {
   409  			return false, nil
   410  		}
   411  
   412  		if pod.DeletionTimestamp == nil {
   413  			return false, errors.New("pod did not have deletion timestamp set")
   414  		}
   415  
   416  		endpoints, err := client.CoreV1().Endpoints(ns.Name).Get(ctx, svc.Name, metav1.GetOptions{})
   417  		if err != nil {
   418  			return false, nil
   419  		}
   420  
   421  		numEndpoints := 0
   422  		for _, subset := range endpoints.Subsets {
   423  			numEndpoints += len(subset.Addresses)
   424  		}
   425  
   426  		if numEndpoints > 0 {
   427  			return false, nil
   428  		}
   429  
   430  		return true, nil
   431  	}); err != nil {
   432  		t.Fatalf("error checking for no endpoints with terminating pods: %v", err)
   433  	}
   434  }
   435  
   436  func labelMap() map[string]string {
   437  	return map[string]string{"foo": "bar"}
   438  }
   439  
   440  // newService returns a service with selector and exposing ports
   441  func newService(namespace, name string) *v1.Service {
   442  	return &v1.Service{
   443  		ObjectMeta: metav1.ObjectMeta{
   444  			Name:      name,
   445  			Namespace: namespace,
   446  			Labels:    labelMap(),
   447  		},
   448  		Spec: v1.ServiceSpec{
   449  			Selector: labelMap(),
   450  			Ports: []v1.ServicePort{
   451  				{Name: "port-1338", Port: 1338, Protocol: "TCP", TargetPort: intstr.FromInt32(1338)},
   452  				{Name: "port-1337", Port: 1337, Protocol: "TCP", TargetPort: intstr.FromInt32(1337)},
   453  			},
   454  		},
   455  	}
   456  }
   457  
   458  // newExternalNameService returns an ExternalName service with selector and exposing ports
   459  func newExternalNameService(namespace, name string) *v1.Service {
   460  	svc := newService(namespace, name)
   461  	svc.Spec.Type = v1.ServiceTypeExternalName
   462  	svc.Spec.ExternalName = "google.com"
   463  	return svc
   464  }