k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/service/service_test.go (about)

     1  /*
     2  Copyright 2022 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 service
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"testing"
    25  	"time"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	discoveryv1 "k8s.io/api/discovery/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/intstr"
    34  	utilrand "k8s.io/apimachinery/pkg/util/rand"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    38  	"k8s.io/client-go/informers"
    39  	clientset "k8s.io/client-go/kubernetes"
    40  	"k8s.io/client-go/tools/cache"
    41  	watchtools "k8s.io/client-go/tools/watch"
    42  	"k8s.io/client-go/util/retry"
    43  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    44  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    45  	"k8s.io/kubernetes/pkg/controller/endpointslice"
    46  	"k8s.io/kubernetes/pkg/features"
    47  	"k8s.io/kubernetes/test/integration/framework"
    48  	"k8s.io/kubernetes/test/utils/format"
    49  	"k8s.io/kubernetes/test/utils/ktesting"
    50  	"k8s.io/utils/ptr"
    51  )
    52  
    53  // Test_ExternalNameServiceStopsDefaultingInternalTrafficPolicy tests that Services no longer default
    54  // the internalTrafficPolicy field when Type is ExternalName. This test exists due to historic reasons where
    55  // the internalTrafficPolicy field was being defaulted in older versions. New versions stop defaulting the
    56  // field and drop on read, but for compatibility reasons we still accept the field.
    57  func Test_ExternalNameServiceStopsDefaultingInternalTrafficPolicy(t *testing.T) {
    58  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
    59  	defer server.TearDownFn()
    60  
    61  	client, err := clientset.NewForConfig(server.ClientConfig)
    62  	if err != nil {
    63  		t.Fatalf("Error creating clientset: %v", err)
    64  	}
    65  
    66  	ns := framework.CreateNamespaceOrDie(client, "test-external-name-drops-internal-traffic-policy", t)
    67  	defer framework.DeleteNamespaceOrDie(client, ns, t)
    68  
    69  	service := &corev1.Service{
    70  		ObjectMeta: metav1.ObjectMeta{
    71  			Name: "test-123",
    72  		},
    73  		Spec: corev1.ServiceSpec{
    74  			Type:         corev1.ServiceTypeExternalName,
    75  			ExternalName: "foo.bar.com",
    76  		},
    77  	}
    78  
    79  	service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{})
    80  	if err != nil {
    81  		t.Fatalf("Error creating test service: %v", err)
    82  	}
    83  
    84  	if service.Spec.InternalTrafficPolicy != nil {
    85  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
    86  	}
    87  
    88  	service, err = client.CoreV1().Services(ns.Name).Get(context.TODO(), service.Name, metav1.GetOptions{})
    89  	if err != nil {
    90  		t.Fatalf("error getting service: %v", err)
    91  	}
    92  
    93  	if service.Spec.InternalTrafficPolicy != nil {
    94  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
    95  	}
    96  }
    97  
    98  // Test_ExternalNameServiceDropsInternalTrafficPolicy tests that Services accepts the internalTrafficPolicy field on Create,
    99  // but drops the field on read. This test exists due to historic reasons where the internalTrafficPolicy field was being defaulted
   100  // in older versions. New versions stop defaulting the field and drop on read, but for compatibility reasons we still accept the field.
   101  func Test_ExternalNameServiceDropsInternalTrafficPolicy(t *testing.T) {
   102  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   103  	defer server.TearDownFn()
   104  
   105  	client, err := clientset.NewForConfig(server.ClientConfig)
   106  	if err != nil {
   107  		t.Fatalf("Error creating clientset: %v", err)
   108  	}
   109  
   110  	ns := framework.CreateNamespaceOrDie(client, "test-external-name-drops-internal-traffic-policy", t)
   111  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   112  
   113  	internalTrafficPolicy := corev1.ServiceInternalTrafficPolicyCluster
   114  	service := &corev1.Service{
   115  		ObjectMeta: metav1.ObjectMeta{
   116  			Name: "test-123",
   117  		},
   118  		Spec: corev1.ServiceSpec{
   119  			Type:                  corev1.ServiceTypeExternalName,
   120  			ExternalName:          "foo.bar.com",
   121  			InternalTrafficPolicy: &internalTrafficPolicy,
   122  		},
   123  	}
   124  
   125  	service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{})
   126  	if err != nil {
   127  		t.Fatalf("Error creating test service: %v", err)
   128  	}
   129  
   130  	if service.Spec.InternalTrafficPolicy != nil {
   131  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
   132  	}
   133  
   134  	service, err = client.CoreV1().Services(ns.Name).Get(context.TODO(), service.Name, metav1.GetOptions{})
   135  	if err != nil {
   136  		t.Fatalf("error getting service: %v", err)
   137  	}
   138  
   139  	if service.Spec.InternalTrafficPolicy != nil {
   140  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
   141  	}
   142  }
   143  
   144  // Test_ConvertingToExternalNameServiceDropsInternalTrafficPolicy tests that converting a Service to Type=ExternalName
   145  // results in the internalTrafficPolicy field being dropped.This test exists due to historic reasons where the internalTrafficPolicy
   146  // field was being defaulted in older versions. New versions stop defaulting the field and drop on read, but for compatibility reasons
   147  // we still accept the field.
   148  func Test_ConvertingToExternalNameServiceDropsInternalTrafficPolicy(t *testing.T) {
   149  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   150  	defer server.TearDownFn()
   151  
   152  	client, err := clientset.NewForConfig(server.ClientConfig)
   153  	if err != nil {
   154  		t.Fatalf("Error creating clientset: %v", err)
   155  	}
   156  
   157  	ns := framework.CreateNamespaceOrDie(client, "test-external-name-drops-internal-traffic-policy", t)
   158  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   159  
   160  	service := &corev1.Service{
   161  		ObjectMeta: metav1.ObjectMeta{
   162  			Name: "test-123",
   163  		},
   164  		Spec: corev1.ServiceSpec{
   165  			Type: corev1.ServiceTypeClusterIP,
   166  			Ports: []corev1.ServicePort{{
   167  				Port: int32(80),
   168  			}},
   169  			Selector: map[string]string{
   170  				"foo": "bar",
   171  			},
   172  		},
   173  	}
   174  
   175  	service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{})
   176  	if err != nil {
   177  		t.Fatalf("Error creating test service: %v", err)
   178  	}
   179  
   180  	if *service.Spec.InternalTrafficPolicy != corev1.ServiceInternalTrafficPolicyCluster {
   181  		t.Error("service internalTrafficPolicy was not set for clusterIP Service")
   182  	}
   183  
   184  	newService := service.DeepCopy()
   185  	newService.Spec.Type = corev1.ServiceTypeExternalName
   186  	newService.Spec.ExternalName = "foo.bar.com"
   187  
   188  	service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), newService, metav1.UpdateOptions{})
   189  	if err != nil {
   190  		t.Fatalf("error updating service: %v", err)
   191  	}
   192  
   193  	if service.Spec.InternalTrafficPolicy != nil {
   194  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
   195  	}
   196  
   197  	service, err = client.CoreV1().Services(ns.Name).Get(context.TODO(), service.Name, metav1.GetOptions{})
   198  	if err != nil {
   199  		t.Fatalf("error getting service: %v", err)
   200  	}
   201  
   202  	if service.Spec.InternalTrafficPolicy != nil {
   203  		t.Errorf("service internalTrafficPolicy should be droppped but is set: %v", service.Spec.InternalTrafficPolicy)
   204  	}
   205  }
   206  
   207  // Test_RemovingExternalIPsFromClusterIPServiceDropsExternalTrafficPolicy tests that removing externalIPs from a
   208  // ClusterIP Service results in the externalTrafficPolicy field being dropped.
   209  func Test_RemovingExternalIPsFromClusterIPServiceDropsExternalTrafficPolicy(t *testing.T) {
   210  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   211  	defer server.TearDownFn()
   212  
   213  	client, err := clientset.NewForConfig(server.ClientConfig)
   214  	if err != nil {
   215  		t.Fatalf("Error creating clientset: %v", err)
   216  	}
   217  
   218  	ns := framework.CreateNamespaceOrDie(client, "test-removing-external-ips-drops-external-traffic-policy", t)
   219  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   220  
   221  	service := &corev1.Service{
   222  		ObjectMeta: metav1.ObjectMeta{
   223  			Name: "test-123",
   224  		},
   225  		Spec: corev1.ServiceSpec{
   226  			Type: corev1.ServiceTypeClusterIP,
   227  			Ports: []corev1.ServicePort{{
   228  				Port: int32(80),
   229  			}},
   230  			Selector: map[string]string{
   231  				"foo": "bar",
   232  			},
   233  			ExternalIPs: []string{"1.1.1.1"},
   234  		},
   235  	}
   236  
   237  	service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{})
   238  	if err != nil {
   239  		t.Fatalf("Error creating test service: %v", err)
   240  	}
   241  
   242  	if service.Spec.ExternalTrafficPolicy != corev1.ServiceExternalTrafficPolicyCluster {
   243  		t.Error("service externalTrafficPolicy was not set for clusterIP Service with externalIPs")
   244  	}
   245  
   246  	// externalTrafficPolicy should be dropped after removing externalIPs.
   247  	newService := service.DeepCopy()
   248  	newService.Spec.ExternalIPs = []string{}
   249  
   250  	service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), newService, metav1.UpdateOptions{})
   251  	if err != nil {
   252  		t.Fatalf("error updating service: %v", err)
   253  	}
   254  
   255  	if service.Spec.ExternalTrafficPolicy != "" {
   256  		t.Errorf("service externalTrafficPolicy should be droppped but is set: %v", service.Spec.ExternalTrafficPolicy)
   257  	}
   258  
   259  	service, err = client.CoreV1().Services(ns.Name).Get(context.TODO(), service.Name, metav1.GetOptions{})
   260  	if err != nil {
   261  		t.Fatalf("error getting service: %v", err)
   262  	}
   263  
   264  	if service.Spec.ExternalTrafficPolicy != "" {
   265  		t.Errorf("service externalTrafficPolicy should be droppped but is set: %v", service.Spec.ExternalTrafficPolicy)
   266  	}
   267  
   268  	// externalTrafficPolicy should be set after adding externalIPs again.
   269  	newService = service.DeepCopy()
   270  	newService.Spec.ExternalIPs = []string{"1.1.1.1"}
   271  
   272  	service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), newService, metav1.UpdateOptions{})
   273  	if err != nil {
   274  		t.Fatalf("error updating service: %v", err)
   275  	}
   276  
   277  	if service.Spec.ExternalTrafficPolicy != corev1.ServiceExternalTrafficPolicyCluster {
   278  		t.Error("service externalTrafficPolicy was not set for clusterIP Service with externalIPs")
   279  	}
   280  
   281  	service, err = client.CoreV1().Services(ns.Name).Get(context.TODO(), service.Name, metav1.GetOptions{})
   282  	if err != nil {
   283  		t.Fatalf("error getting service: %v", err)
   284  	}
   285  
   286  	if service.Spec.ExternalTrafficPolicy != corev1.ServiceExternalTrafficPolicyCluster {
   287  		t.Error("service externalTrafficPolicy was not set for clusterIP Service with externalIPs")
   288  	}
   289  }
   290  
   291  // Test transitions involving the `trafficDistribution` field in Service spec.
   292  func Test_TransitionsForTrafficDistribution(t *testing.T) {
   293  
   294  	////////////////////////////////////////////////////////////////////////////
   295  	// Setup components, like kube-apiserver and EndpointSlice controller.
   296  	////////////////////////////////////////////////////////////////////////////
   297  
   298  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceTrafficDistribution, true)
   299  
   300  	// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   301  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins=ServiceAccount"}, framework.SharedEtcd())
   302  	defer server.TearDownFn()
   303  
   304  	client, err := clientset.NewForConfig(server.ClientConfig)
   305  	if err != nil {
   306  		t.Fatalf("Error creating clientset: %v", err)
   307  	}
   308  
   309  	resyncPeriod := 12 * time.Hour
   310  	informers := informers.NewSharedInformerFactory(client, resyncPeriod)
   311  
   312  	ctx := ktesting.Init(t)
   313  	defer ctx.Cancel("test has completed")
   314  	epsController := endpointslice.NewController(
   315  		ctx,
   316  		informers.Core().V1().Pods(),
   317  		informers.Core().V1().Services(),
   318  		informers.Core().V1().Nodes(),
   319  		informers.Discovery().V1().EndpointSlices(),
   320  		int32(100),
   321  		client,
   322  		1*time.Second,
   323  	)
   324  
   325  	informers.Start(ctx.Done())
   326  	go epsController.Run(ctx, 1)
   327  
   328  	////////////////////////////////////////////////////////////////////////////
   329  	// Create a namespace, node, pod in the node, and a service exposing the pod.
   330  	////////////////////////////////////////////////////////////////////////////
   331  
   332  	ns := framework.CreateNamespaceOrDie(client, "test-service-traffic-distribution", t)
   333  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   334  
   335  	node := &corev1.Node{
   336  		ObjectMeta: metav1.ObjectMeta{
   337  			Name: "fake-node",
   338  			Labels: map[string]string{
   339  				corev1.LabelTopologyZone: "fake-zone-1",
   340  			},
   341  		},
   342  	}
   343  
   344  	pod := &corev1.Pod{
   345  		ObjectMeta: metav1.ObjectMeta{
   346  			Name:      "test-pod",
   347  			Namespace: ns.GetName(),
   348  			Labels: map[string]string{
   349  				"foo": "bar",
   350  			},
   351  		},
   352  		Spec: corev1.PodSpec{
   353  			NodeName: node.GetName(),
   354  			Containers: []corev1.Container{
   355  				{
   356  					Name:  "fake-name",
   357  					Image: "fake-image",
   358  					Ports: []corev1.ContainerPort{
   359  						{
   360  							Name:          "port-443",
   361  							ContainerPort: 443,
   362  						},
   363  					},
   364  				},
   365  			},
   366  		},
   367  		Status: corev1.PodStatus{
   368  			Phase: corev1.PodRunning,
   369  			Conditions: []corev1.PodCondition{
   370  				{
   371  					Type:   corev1.PodReady,
   372  					Status: corev1.ConditionTrue,
   373  				},
   374  			},
   375  			PodIP: "10.0.0.1",
   376  			PodIPs: []corev1.PodIP{
   377  				{
   378  					IP: "10.0.0.1",
   379  				},
   380  			},
   381  		},
   382  	}
   383  
   384  	svc := &corev1.Service{
   385  		ObjectMeta: metav1.ObjectMeta{
   386  			Name:      "test-service",
   387  			Namespace: ns.GetName(),
   388  		},
   389  		Spec: corev1.ServiceSpec{
   390  			Selector: map[string]string{
   391  				"foo": "bar",
   392  			},
   393  			Ports: []corev1.ServicePort{
   394  				{Name: "port-443", Port: 443, Protocol: "TCP", TargetPort: intstr.FromInt32(443)},
   395  			},
   396  		},
   397  	}
   398  
   399  	_, err = client.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{})
   400  	if err != nil {
   401  		t.Fatalf("Failed to create test node: %v", err)
   402  	}
   403  	_, err = client.CoreV1().Pods(ns.Name).Create(ctx, pod, metav1.CreateOptions{})
   404  	if err != nil {
   405  		t.Fatalf("Failed to create test ready pod: %v", err)
   406  	}
   407  	_, err = client.CoreV1().Pods(ns.Name).UpdateStatus(ctx, pod, metav1.UpdateOptions{})
   408  	if err != nil {
   409  		t.Fatalf("Failed to update status for test pod to Ready: %v", err)
   410  	}
   411  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, svc, metav1.CreateOptions{})
   412  	if err != nil {
   413  		t.Fatalf("Failed to create test service: %v", err)
   414  	}
   415  
   416  	////////////////////////////////////////////////////////////////////////////
   417  	// Assert that without the presence of `trafficDistribution` field and the
   418  	// service.kubernetes.io/topology-mode=Auto annotation, there are no zone
   419  	// hints in EndpointSlice.
   420  	////////////////////////////////////////////////////////////////////////////
   421  
   422  	// logsBuffer captures logs during assertions which multiple retires. These
   423  	// will only be printed if the assertion failed.
   424  	logsBuffer := &bytes.Buffer{}
   425  
   426  	endpointSlicesHaveNoHints := func(ctx context.Context) (bool, error) {
   427  		slices, err := client.DiscoveryV1().EndpointSlices(ns.GetName()).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, svc.GetName())})
   428  		if err != nil {
   429  			fmt.Fprintf(logsBuffer, "failed to list EndpointSlices for service %q: %v\n", svc.GetName(), err)
   430  			return false, nil
   431  		}
   432  		if slices == nil || len(slices.Items) == 0 {
   433  			fmt.Fprintf(logsBuffer, "no EndpointSlices returned for service %q\n", svc.GetName())
   434  			return false, nil
   435  		}
   436  		fmt.Fprintf(logsBuffer, "EndpointSlices=\n%v\n", format.Object(slices, 1 /* indent one level */))
   437  
   438  		for _, slice := range slices.Items {
   439  			for _, endpoint := range slice.Endpoints {
   440  				var ip string
   441  				if len(endpoint.Addresses) > 0 {
   442  					ip = endpoint.Addresses[0]
   443  				}
   444  				if endpoint.Hints != nil && len(endpoint.Hints.ForZones) != 0 {
   445  					fmt.Fprintf(logsBuffer, "endpoint with ip %v has hint %+v, want no hint\n", ip, endpoint.Hints)
   446  					return false, nil
   447  				}
   448  			}
   449  		}
   450  		return true, nil
   451  	}
   452  
   453  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, endpointSlicesHaveNoHints)
   454  	if err != nil {
   455  		t.Logf("logsBuffer=\n%v", logsBuffer)
   456  		t.Fatalf("Error waiting for EndpointSlices to have same zone hints: %v", err)
   457  	}
   458  	logsBuffer.Reset()
   459  
   460  	////////////////////////////////////////////////////////////////////////////
   461  	// Update the service by setting the `trafficDistribution: PreferLocal` field
   462  	//
   463  	// Assert that the respective EndpointSlices get the same-zone hints.
   464  	////////////////////////////////////////////////////////////////////////////
   465  
   466  	trafficDist := corev1.ServiceTrafficDistributionPreferClose
   467  	svc.Spec.TrafficDistribution = &trafficDist
   468  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc, metav1.UpdateOptions{})
   469  	if err != nil {
   470  		t.Fatalf("Failed to update test service with 'trafficDistribution: PreferLocal': %v", err)
   471  	}
   472  
   473  	endpointSlicesHaveSameZoneHints := func(ctx context.Context) (bool, error) {
   474  		slices, err := client.DiscoveryV1().EndpointSlices(ns.GetName()).List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, svc.GetName())})
   475  		if err != nil {
   476  			fmt.Fprintf(logsBuffer, "failed to list EndpointSlices for service %q: %v\n", svc.GetName(), err)
   477  			return false, nil
   478  		}
   479  		if slices == nil || len(slices.Items) == 0 {
   480  			fmt.Fprintf(logsBuffer, "no EndpointSlices returned for service %q\n", svc.GetName())
   481  			return false, nil
   482  		}
   483  		fmt.Fprintf(logsBuffer, "EndpointSlices=\n%v\n", format.Object(slices, 1 /* indent one level */))
   484  
   485  		for _, slice := range slices.Items {
   486  			for _, endpoint := range slice.Endpoints {
   487  				var ip string
   488  				if len(endpoint.Addresses) > 0 {
   489  					ip = endpoint.Addresses[0]
   490  				}
   491  				var zone string
   492  				if endpoint.Zone != nil {
   493  					zone = *endpoint.Zone
   494  				}
   495  				if endpoint.Hints == nil || len(endpoint.Hints.ForZones) != 1 || endpoint.Hints.ForZones[0].Name != zone {
   496  					fmt.Fprintf(logsBuffer, "endpoint with ip %v does not have the correct hint, want hint for zone %q\n", ip, zone)
   497  					return false, nil
   498  				}
   499  			}
   500  		}
   501  		return true, nil
   502  	}
   503  
   504  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, endpointSlicesHaveSameZoneHints)
   505  	if err != nil {
   506  		t.Logf("logsBuffer=\n%v", logsBuffer)
   507  		t.Fatalf("Error waiting for EndpointSlices to have same zone hints: %v", err)
   508  	}
   509  	logsBuffer.Reset()
   510  
   511  	////////////////////////////////////////////////////////////////////////////
   512  	// Update the service with the service.kubernetes.io/topology-mode=Auto
   513  	// annotation.
   514  	//
   515  	// Assert that the EndpointSlice for service have no hints once
   516  	// service.kubernetes.io/topology-mode=Auto takes affect, since topology
   517  	// annotation would not work with only one service pod.
   518  	////////////////////////////////////////////////////////////////////////////
   519  	svc.Annotations = map[string]string{corev1.AnnotationTopologyMode: "Auto"}
   520  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc, metav1.UpdateOptions{})
   521  	if err != nil {
   522  		t.Fatalf("Failed to update test service with 'service.kubernetes.io/topology-mode=Auto' annotation: %v", err)
   523  	}
   524  
   525  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, endpointSlicesHaveNoHints)
   526  	if err != nil {
   527  		t.Logf("logsBuffer=\n%v", logsBuffer)
   528  		t.Fatalf("Error waiting for EndpointSlices to have no hints: %v", err)
   529  	}
   530  	logsBuffer.Reset()
   531  
   532  	////////////////////////////////////////////////////////////////////////////
   533  	// Remove the annotation service.kubernetes.io/topology-mode=Auto from the
   534  	// service.
   535  	//
   536  	// Assert that EndpointSlice for service again has the correct same-zone
   537  	// hints because of the `trafficDistribution: PreferLocal` field.
   538  	////////////////////////////////////////////////////////////////////////////
   539  	svc.Annotations = map[string]string{}
   540  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc, metav1.UpdateOptions{})
   541  	if err != nil {
   542  		t.Fatalf("Failed to remove annotation 'service.kubernetes.io/topology-mode=Auto' from service: %v", err)
   543  	}
   544  
   545  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, endpointSlicesHaveSameZoneHints)
   546  	if err != nil {
   547  		t.Logf("logsBuffer=\n%v", logsBuffer)
   548  		t.Fatalf("Error waiting for EndpointSlices to have same zone hints: %v", err)
   549  	}
   550  	logsBuffer.Reset()
   551  
   552  	////////////////////////////////////////////////////////////////////////////
   553  	// Remove the field `trafficDistribution: PreferLocal` from the service.
   554  	//
   555  	// Assert that EndpointSlice for service again has no zone hints.
   556  	////////////////////////////////////////////////////////////////////////////
   557  	svc.Spec.TrafficDistribution = nil
   558  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, svc, metav1.UpdateOptions{})
   559  	if err != nil {
   560  		t.Fatalf("Failed to remove annotation 'service.kubernetes.io/topology-mode=Auto' from service: %v", err)
   561  	}
   562  
   563  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, endpointSlicesHaveNoHints)
   564  	if err != nil {
   565  		t.Logf("logsBuffer=\n%v", logsBuffer)
   566  		t.Fatalf("Error waiting for EndpointSlices to have no hints: %v", err)
   567  	}
   568  	logsBuffer.Reset()
   569  }
   570  
   571  func Test_ServiceClusterIPSelector(t *testing.T) {
   572  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   573  	defer server.TearDownFn()
   574  
   575  	ctx, cancel := context.WithCancel(context.Background())
   576  	defer cancel()
   577  
   578  	client, err := clientset.NewForConfig(server.ClientConfig)
   579  	if err != nil {
   580  		t.Fatalf("Error creating clientset: %v", err)
   581  	}
   582  
   583  	ns := framework.CreateNamespaceOrDie(client, "test-external-name-drops-internal-traffic-policy", t)
   584  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   585  
   586  	// create headless service
   587  	service := &corev1.Service{
   588  		ObjectMeta: metav1.ObjectMeta{
   589  			Name:      "test-headless",
   590  			Namespace: ns.Name,
   591  		},
   592  		Spec: corev1.ServiceSpec{
   593  			ClusterIP: corev1.ClusterIPNone,
   594  			Type:      corev1.ServiceTypeClusterIP,
   595  			Ports: []corev1.ServicePort{{
   596  				Port: int32(80),
   597  			}},
   598  			Selector: map[string]string{
   599  				"foo": "bar",
   600  			},
   601  		},
   602  	}
   603  
   604  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{})
   605  	if err != nil {
   606  		t.Fatalf("Error creating test service: %v", err)
   607  	}
   608  
   609  	// informer to watch only non-headless services
   610  	kubeInformers := informers.NewSharedInformerFactoryWithOptions(client, 0, informers.WithTweakListOptions(func(options *metav1.ListOptions) {
   611  		options.FieldSelector = fields.OneTermNotEqualSelector("spec.clusterIP", corev1.ClusterIPNone).String()
   612  	}))
   613  
   614  	serviceInformer := kubeInformers.Core().V1().Services().Informer()
   615  	serviceLister := kubeInformers.Core().V1().Services().Lister()
   616  	serviceHasSynced := serviceInformer.HasSynced
   617  	if _, err = serviceInformer.AddEventHandler(
   618  		cache.ResourceEventHandlerFuncs{
   619  			AddFunc: func(obj interface{}) {
   620  				svc := obj.(*corev1.Service)
   621  				t.Logf("Added Service %#v", svc)
   622  			},
   623  			UpdateFunc: func(oldObj, newObj interface{}) {
   624  				oldSvc := oldObj.(*corev1.Service)
   625  				newSvc := newObj.(*corev1.Service)
   626  				t.Logf("Updated Service %#v to %#v", oldSvc, newSvc)
   627  			},
   628  			DeleteFunc: func(obj interface{}) {
   629  				svc := obj.(*corev1.Service)
   630  				t.Logf("Deleted Service %#v", svc)
   631  			},
   632  		},
   633  	); err != nil {
   634  		t.Fatalf("Error adding service informer handler: %v", err)
   635  	}
   636  	kubeInformers.Start(ctx.Done())
   637  	cache.WaitForCacheSync(ctx.Done(), serviceHasSynced)
   638  	svcs, err := serviceLister.List(labels.Everything())
   639  	if err != nil {
   640  		t.Fatalf("Error listing services: %v", err)
   641  	}
   642  	// only the kubernetes.default service expected
   643  	if len(svcs) != 1 || svcs[0].Name != "kubernetes" {
   644  		t.Fatalf("expected 1 services, got %d", len(svcs))
   645  	}
   646  
   647  	// create a new service with ClusterIP
   648  	service2 := service.DeepCopy()
   649  	service2.Spec.ClusterIP = ""
   650  	service2.Name = "test-clusterip"
   651  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, service2, metav1.CreateOptions{})
   652  	if err != nil {
   653  		t.Fatalf("Error creating test service: %v", err)
   654  	}
   655  
   656  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, func(ctx context.Context) (done bool, err error) {
   657  		svc, err := serviceLister.Services(service2.Namespace).Get(service2.Name)
   658  		if svc == nil || err != nil {
   659  			return false, nil
   660  		}
   661  		return true, nil
   662  	})
   663  	if err != nil {
   664  		t.Fatalf("Error waiting for test service test-clusterip: %v", err)
   665  	}
   666  
   667  	// mutate the Service to drop the ClusterIP, theoretically ClusterIP is inmutable but ...
   668  	service.Spec.ExternalName = "test"
   669  	service.Spec.Type = corev1.ServiceTypeExternalName
   670  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, service, metav1.UpdateOptions{})
   671  	if err != nil {
   672  		t.Fatalf("Error creating test service: %v", err)
   673  	}
   674  
   675  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, func(ctx context.Context) (done bool, err error) {
   676  		svc, err := serviceLister.Services(service.Namespace).Get(service.Name)
   677  		if svc == nil || err != nil {
   678  			return false, nil
   679  		}
   680  		return true, nil
   681  	})
   682  	if err != nil {
   683  		t.Fatalf("Error waiting for test service without ClusterIP: %v", err)
   684  	}
   685  
   686  	// mutate the Service to get the ClusterIP again
   687  	service.Spec.ExternalName = ""
   688  	service.Spec.ClusterIP = ""
   689  	service.Spec.Type = corev1.ServiceTypeClusterIP
   690  	_, err = client.CoreV1().Services(ns.Name).Update(ctx, service, metav1.UpdateOptions{})
   691  	if err != nil {
   692  		t.Fatalf("Error creating test service: %v", err)
   693  	}
   694  
   695  	err = wait.PollUntilContextTimeout(ctx, 1*time.Second, 10*time.Second, true, func(ctx context.Context) (done bool, err error) {
   696  		svc, err := serviceLister.Services(service.Namespace).Get(service.Name)
   697  		if svc == nil || err != nil {
   698  			return false, nil
   699  		}
   700  		return true, nil
   701  	})
   702  	if err != nil {
   703  		t.Fatalf("Error waiting for test service with ClusterIP: %v", err)
   704  	}
   705  }
   706  
   707  // Repro https://github.com/kubernetes/kubernetes/issues/123853
   708  func Test_ServiceWatchUntil(t *testing.T) {
   709  	svcReadyTimeout := 30 * time.Second
   710  
   711  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   712  	defer server.TearDownFn()
   713  
   714  	ctx, cancel := context.WithCancel(context.Background())
   715  	defer cancel()
   716  
   717  	client, err := clientset.NewForConfig(server.ClientConfig)
   718  	if err != nil {
   719  		t.Fatalf("Error creating clientset: %v", err)
   720  	}
   721  
   722  	ns := framework.CreateNamespaceOrDie(client, "test-service-watchuntil", t)
   723  	defer framework.DeleteNamespaceOrDie(client, ns, t)
   724  
   725  	testSvcName := "test-service-" + utilrand.String(5)
   726  	testSvcLabels := map[string]string{"test-service-static": "true"}
   727  	testSvcLabelsFlat := "test-service-static=true"
   728  
   729  	w := &cache.ListWatch{
   730  		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   731  			options.LabelSelector = testSvcLabelsFlat
   732  			return client.CoreV1().Services(ns.Name).Watch(ctx, options)
   733  		},
   734  	}
   735  
   736  	svcList, err := client.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: testSvcLabelsFlat})
   737  	if err != nil {
   738  		t.Fatalf("failed to list Services: %v", err)
   739  	}
   740  	// create  service
   741  	service := &corev1.Service{
   742  		ObjectMeta: metav1.ObjectMeta{
   743  			Name:   testSvcName,
   744  			Labels: testSvcLabels,
   745  		},
   746  		Spec: corev1.ServiceSpec{
   747  			Type: "LoadBalancer",
   748  			Ports: []corev1.ServicePort{{
   749  				Name:       "http",
   750  				Protocol:   corev1.ProtocolTCP,
   751  				Port:       int32(80),
   752  				TargetPort: intstr.FromInt32(80),
   753  			}},
   754  			LoadBalancerClass: ptr.To[string]("example.com/internal-vip"),
   755  		},
   756  	}
   757  	_, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{})
   758  	if err != nil {
   759  		t.Fatalf("Error creating test service: %v", err)
   760  	}
   761  
   762  	ctxUntil, cancel := context.WithTimeout(ctx, svcReadyTimeout)
   763  	defer cancel()
   764  	_, err = watchtools.Until(ctxUntil, svcList.ResourceVersion, w, func(event watch.Event) (bool, error) {
   765  		if svc, ok := event.Object.(*corev1.Service); ok {
   766  			found := svc.ObjectMeta.Name == service.ObjectMeta.Name &&
   767  				svc.ObjectMeta.Namespace == ns.Name &&
   768  				svc.Labels["test-service-static"] == "true"
   769  			if !found {
   770  				t.Logf("observed Service %v in namespace %v with labels: %v & ports %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels, svc.Spec.Ports)
   771  				return false, nil
   772  			}
   773  			t.Logf("Found Service %v in namespace %v with labels: %v & ports %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels, svc.Spec.Ports)
   774  			return found, nil
   775  		}
   776  		t.Logf("Observed event: %+v", event.Object)
   777  		return false, nil
   778  	})
   779  	if err != nil {
   780  		t.Fatalf("Error service not found: %v", err)
   781  	}
   782  
   783  	t.Log("patching the ServiceStatus")
   784  	lbStatus := corev1.LoadBalancerStatus{
   785  		Ingress: []corev1.LoadBalancerIngress{{IP: "203.0.113.1"}},
   786  	}
   787  	lbStatusJSON, err := json.Marshal(lbStatus)
   788  	if err != nil {
   789  		t.Fatalf("Error marshalling status: %v", err)
   790  	}
   791  	_, err = client.CoreV1().Services(ns.Name).Patch(ctx, testSvcName, types.MergePatchType,
   792  		[]byte(`{"metadata":{"annotations":{"patchedstatus":"true"}},"status":{"loadBalancer":`+string(lbStatusJSON)+`}}`),
   793  		metav1.PatchOptions{}, "status")
   794  	if err != nil {
   795  		t.Fatalf("Could not patch service status: %v", err)
   796  	}
   797  
   798  	t.Log("watching for the Service to be patched")
   799  	ctxUntil, cancel = context.WithTimeout(ctx, svcReadyTimeout)
   800  	defer cancel()
   801  
   802  	_, err = watchtools.Until(ctxUntil, svcList.ResourceVersion, w, func(event watch.Event) (bool, error) {
   803  		if svc, ok := event.Object.(*corev1.Service); ok {
   804  			found := svc.ObjectMeta.Name == service.ObjectMeta.Name &&
   805  				svc.ObjectMeta.Namespace == ns.Name &&
   806  				svc.Annotations["patchedstatus"] == "true"
   807  			if !found {
   808  				t.Logf("observed Service %v in namespace %v with annotations: %v & LoadBalancer: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Annotations, svc.Status.LoadBalancer)
   809  				return false, nil
   810  			}
   811  			t.Logf("Found Service %v in namespace %v with annotations: %v & LoadBalancer: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Annotations, svc.Status.LoadBalancer)
   812  			return found, nil
   813  		}
   814  		t.Logf("Observed event: %+v", event.Object)
   815  		return false, nil
   816  	})
   817  	if err != nil {
   818  		t.Fatalf("failed to locate Service %v in namespace %v", service.ObjectMeta.Name, ns)
   819  	}
   820  	t.Logf("Service %s has service status patched", testSvcName)
   821  
   822  	t.Log("updating the ServiceStatus")
   823  
   824  	var statusToUpdate, updatedStatus *corev1.Service
   825  	err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
   826  		statusToUpdate, err = client.CoreV1().Services(ns.Name).Get(ctx, testSvcName, metav1.GetOptions{})
   827  		if err != nil {
   828  			return err
   829  		}
   830  
   831  		statusToUpdate.Status.Conditions = append(statusToUpdate.Status.Conditions, metav1.Condition{
   832  			Type:    "StatusUpdate",
   833  			Status:  metav1.ConditionTrue,
   834  			Reason:  "E2E",
   835  			Message: "Set from e2e test",
   836  		})
   837  
   838  		updatedStatus, err = client.CoreV1().Services(ns.Name).UpdateStatus(ctx, statusToUpdate, metav1.UpdateOptions{})
   839  		return err
   840  	})
   841  	if err != nil {
   842  		t.Fatalf("\n\n Failed to UpdateStatus. %v\n\n", err)
   843  	}
   844  	t.Logf("updatedStatus.Conditions: %#v", updatedStatus.Status.Conditions)
   845  
   846  	t.Log("watching for the Service to be updated")
   847  	ctxUntil, cancel = context.WithTimeout(ctx, svcReadyTimeout)
   848  	defer cancel()
   849  	_, err = watchtools.Until(ctxUntil, svcList.ResourceVersion, w, func(event watch.Event) (bool, error) {
   850  		if svc, ok := event.Object.(*corev1.Service); ok {
   851  			found := svc.ObjectMeta.Name == service.ObjectMeta.Name &&
   852  				svc.ObjectMeta.Namespace == ns.Name &&
   853  				svc.Annotations["patchedstatus"] == "true"
   854  			if !found {
   855  				t.Logf("Observed Service %v in namespace %v with annotations: %v & Conditions: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Annotations, svc.Status.LoadBalancer)
   856  				return false, nil
   857  			}
   858  			for _, cond := range svc.Status.Conditions {
   859  				if cond.Type == "StatusUpdate" &&
   860  					cond.Reason == "E2E" &&
   861  					cond.Message == "Set from e2e test" {
   862  					t.Logf("Found Service %v in namespace %v with annotations: %v & Conditions: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Annotations, svc.Status.Conditions)
   863  					return found, nil
   864  				} else {
   865  					t.Logf("Observed Service %v in namespace %v with annotations: %v & Conditions: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Annotations, svc.Status.LoadBalancer)
   866  					return false, nil
   867  				}
   868  			}
   869  		}
   870  		t.Logf("Observed event: %+v", event.Object)
   871  		return false, nil
   872  	})
   873  	if err != nil {
   874  		t.Fatalf("failed to locate Service %v in namespace %v", service.ObjectMeta.Name, ns)
   875  	}
   876  	t.Logf("Service %s has service status updated", testSvcName)
   877  
   878  	t.Log("patching the service")
   879  	servicePatchPayload, err := json.Marshal(corev1.Service{
   880  		ObjectMeta: metav1.ObjectMeta{
   881  			Labels: map[string]string{
   882  				"test-service": "patched",
   883  			},
   884  		},
   885  	})
   886  
   887  	_, err = client.CoreV1().Services(ns.Name).Patch(ctx, testSvcName, types.StrategicMergePatchType, []byte(servicePatchPayload), metav1.PatchOptions{})
   888  	if err != nil {
   889  		t.Fatalf("failed to patch service. %v", err)
   890  	}
   891  
   892  	t.Log("watching for the Service to be patched")
   893  	ctxUntil, cancel = context.WithTimeout(ctx, svcReadyTimeout)
   894  	defer cancel()
   895  	_, err = watchtools.Until(ctxUntil, svcList.ResourceVersion, w, func(event watch.Event) (bool, error) {
   896  		if svc, ok := event.Object.(*corev1.Service); ok {
   897  			found := svc.ObjectMeta.Name == service.ObjectMeta.Name &&
   898  				svc.ObjectMeta.Namespace == ns.Name &&
   899  				svc.Labels["test-service"] == "patched"
   900  			if !found {
   901  				t.Logf("observed Service %v in namespace %v with labels: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels)
   902  				return false, nil
   903  			}
   904  			t.Logf("Found Service %v in namespace %v with labels: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels)
   905  			return found, nil
   906  		}
   907  		t.Logf("Observed event: %+v", event.Object)
   908  		return false, nil
   909  	})
   910  	if err != nil {
   911  		t.Fatalf("failed to locate Service %v in namespace %v", service.ObjectMeta.Name, ns)
   912  	}
   913  
   914  	t.Logf("Service %s patched", testSvcName)
   915  
   916  	t.Log("deleting the service")
   917  	err = client.CoreV1().Services(ns.Name).Delete(ctx, testSvcName, metav1.DeleteOptions{})
   918  	if err != nil {
   919  		t.Fatalf("failed to delete the Service. %v", err)
   920  	}
   921  
   922  	t.Log("watching for the Service to be deleted")
   923  	ctxUntil, cancel = context.WithTimeout(ctx, svcReadyTimeout)
   924  	defer cancel()
   925  	_, err = watchtools.Until(ctxUntil, svcList.ResourceVersion, w, func(event watch.Event) (bool, error) {
   926  		switch event.Type {
   927  		case watch.Deleted:
   928  			if svc, ok := event.Object.(*corev1.Service); ok {
   929  				found := svc.ObjectMeta.Name == service.ObjectMeta.Name &&
   930  					svc.ObjectMeta.Namespace == ns.Name &&
   931  					svc.Labels["test-service-static"] == "true"
   932  				if !found {
   933  					t.Logf("observed Service %v in namespace %v with labels: %v & annotations: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels, svc.Annotations)
   934  					return false, nil
   935  				}
   936  				t.Logf("Found Service %v in namespace %v with labels: %v & annotations: %v", svc.ObjectMeta.Name, svc.ObjectMeta.Namespace, svc.Labels, svc.Annotations)
   937  				return found, nil
   938  			}
   939  		default:
   940  			t.Logf("Observed event: %+v", event.Type)
   941  		}
   942  		return false, nil
   943  	})
   944  	if err != nil {
   945  		t.Fatalf("failed to delete Service %v in namespace %v", service.ObjectMeta.Name, ns)
   946  	}
   947  	t.Logf("Service %s deleted", testSvcName)
   948  }