k8s.io/kubernetes@v1.29.3/test/e2e/network/endpointslicemirroring.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 network
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"time"
    24  
    25  	"github.com/onsi/ginkgo/v2"
    26  	v1 "k8s.io/api/core/v1"
    27  	discoveryv1 "k8s.io/api/discovery/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	"k8s.io/kubernetes/test/e2e/framework"
    32  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    33  	"k8s.io/kubernetes/test/e2e/network/common"
    34  	admissionapi "k8s.io/pod-security-admission/api"
    35  )
    36  
    37  var _ = common.SIGDescribe("EndpointSliceMirroring", func() {
    38  	f := framework.NewDefaultFramework("endpointslicemirroring")
    39  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    40  
    41  	var cs clientset.Interface
    42  
    43  	ginkgo.BeforeEach(func() {
    44  		cs = f.ClientSet
    45  	})
    46  
    47  	/*
    48  		Release: v1.21
    49  		Testname: EndpointSlice Mirroring
    50  		Description: The discovery.k8s.io API group MUST exist in the /apis discovery document.
    51  		The discovery.k8s.io/v1 API group/version MUST exist in the /apis/discovery.k8s.io discovery document.
    52  		The endpointslices resource MUST exist in the /apis/discovery.k8s.io/v1 discovery document.
    53  		The endpointslices mirrorowing must mirror endpoint create, update, and delete actions.
    54  	*/
    55  	framework.ConformanceIt("should mirror a custom Endpoints resource through create update and delete", func(ctx context.Context) {
    56  		svc := createServiceReportErr(ctx, cs, f.Namespace.Name, &v1.Service{
    57  			ObjectMeta: metav1.ObjectMeta{
    58  				Name: "example-custom-endpoints",
    59  			},
    60  			Spec: v1.ServiceSpec{
    61  				Ports: []v1.ServicePort{{
    62  					Name:     "example",
    63  					Port:     80,
    64  					Protocol: v1.ProtocolTCP,
    65  				}},
    66  			},
    67  		})
    68  
    69  		endpoints := &v1.Endpoints{
    70  			ObjectMeta: metav1.ObjectMeta{
    71  				Name: svc.Name,
    72  			},
    73  			Subsets: []v1.EndpointSubset{{
    74  				Ports: []v1.EndpointPort{{
    75  					Port: 80,
    76  				}},
    77  				Addresses: []v1.EndpointAddress{{
    78  					IP: "10.1.2.3",
    79  				}},
    80  			}},
    81  		}
    82  
    83  		ginkgo.By("mirroring a new custom Endpoint", func() {
    84  			_, err := cs.CoreV1().Endpoints(f.Namespace.Name).Create(ctx, endpoints, metav1.CreateOptions{})
    85  			framework.ExpectNoError(err, "Unexpected error creating Endpoints")
    86  
    87  			if err := wait.PollImmediate(2*time.Second, 12*time.Second, func() (bool, error) {
    88  				esList, err := cs.DiscoveryV1().EndpointSlices(f.Namespace.Name).List(ctx, metav1.ListOptions{
    89  					LabelSelector: discoveryv1.LabelServiceName + "=" + svc.Name,
    90  				})
    91  				if err != nil {
    92  					framework.Logf("Error listing EndpointSlices: %v", err)
    93  					return false, nil
    94  				}
    95  				if len(esList.Items) == 0 {
    96  					framework.Logf("Waiting for at least 1 EndpointSlice to exist, got %d", len(esList.Items))
    97  					return false, nil
    98  				}
    99  
   100  				// Due to informer caching, it's possible for the controller
   101  				// to create a second EndpointSlice if it does not see the
   102  				// first EndpointSlice that was created during a sync. All
   103  				// EndpointSlices created should be valid though.
   104  				for _, epSlice := range esList.Items {
   105  					if len(epSlice.Ports) != 1 {
   106  						return false, fmt.Errorf("Expected EndpointSlice to have 1 Port, got %d", len(epSlice.Ports))
   107  					}
   108  					port := epSlice.Ports[0]
   109  					if *port.Port != int32(80) {
   110  						return false, fmt.Errorf("Expected port to be 80, got %d", *port.Port)
   111  					}
   112  					if len(epSlice.Endpoints) != 1 {
   113  						return false, fmt.Errorf("Expected EndpointSlice to have 1 endpoints, got %d", len(epSlice.Endpoints))
   114  					}
   115  					endpoint := epSlice.Endpoints[0]
   116  					if len(endpoint.Addresses) != 1 {
   117  						return false, fmt.Errorf("Expected EndpointSlice endpoint to have 1 address, got %d", len(endpoint.Addresses))
   118  					}
   119  					address := endpoint.Addresses[0]
   120  					if address != "10.1.2.3" {
   121  						return false, fmt.Errorf("Expected EndpointSlice to have 10.1.2.3 as address, got %s", address)
   122  					}
   123  				}
   124  
   125  				return true, nil
   126  			}); err != nil {
   127  				framework.Failf("Did not find matching EndpointSlice for %s/%s: %s", svc.Namespace, svc.Name, err)
   128  			}
   129  		})
   130  
   131  		ginkgo.By("mirroring an update to a custom Endpoint", func() {
   132  			endpoints.Subsets[0].Addresses = []v1.EndpointAddress{{
   133  				IP: "10.2.3.4",
   134  			}}
   135  			_, err := cs.CoreV1().Endpoints(f.Namespace.Name).Update(ctx, endpoints, metav1.UpdateOptions{})
   136  			framework.ExpectNoError(err, "Unexpected error updating Endpoints")
   137  
   138  			// Expect mirrored EndpointSlice resource to be updated.
   139  			if err := wait.PollImmediate(2*time.Second, 12*time.Second, func() (bool, error) {
   140  				esList, err := cs.DiscoveryV1().EndpointSlices(f.Namespace.Name).List(ctx, metav1.ListOptions{
   141  					LabelSelector: discoveryv1.LabelServiceName + "=" + svc.Name,
   142  				})
   143  				if err != nil {
   144  					return false, err
   145  				}
   146  				if len(esList.Items) != 1 {
   147  					framework.Logf("Waiting for 1 EndpointSlice to exist, got %d", len(esList.Items))
   148  					return false, nil
   149  				}
   150  				epSlice := esList.Items[0]
   151  				if len(epSlice.Ports) != 1 {
   152  					framework.Logf("Expected EndpointSlice to have 1 Port, got %d", len(epSlice.Ports))
   153  					return false, nil
   154  				}
   155  				port := epSlice.Ports[0]
   156  				if *port.Port != int32(80) {
   157  					framework.Logf("Expected port to be 80, got %d", *port.Port)
   158  					return false, nil
   159  				}
   160  				if len(epSlice.Endpoints) != 1 {
   161  					framework.Logf("Expected EndpointSlice to have 1 endpoints, got %d", len(epSlice.Endpoints))
   162  					return false, nil
   163  				}
   164  				endpoint := epSlice.Endpoints[0]
   165  				if len(endpoint.Addresses) != 1 {
   166  					framework.Logf("Expected EndpointSlice endpoint to have 1 address, got %d", len(endpoint.Addresses))
   167  					return false, nil
   168  				}
   169  				address := endpoint.Addresses[0]
   170  				if address != "10.2.3.4" {
   171  					framework.Logf("Expected EndpointSlice to have 10.2.3.4 as address, got %s", address)
   172  					return false, nil
   173  				}
   174  
   175  				return true, nil
   176  			}); err != nil {
   177  				framework.Failf("Did not find matching EndpointSlice for %s/%s: %s", svc.Namespace, svc.Name, err)
   178  			}
   179  		})
   180  
   181  		ginkgo.By("mirroring deletion of a custom Endpoint", func() {
   182  			err := cs.CoreV1().Endpoints(f.Namespace.Name).Delete(ctx, endpoints.Name, metav1.DeleteOptions{})
   183  			framework.ExpectNoError(err, "Unexpected error deleting Endpoints")
   184  
   185  			// Expect mirrored EndpointSlice resource to be updated.
   186  			if err := wait.PollImmediate(2*time.Second, 12*time.Second, func() (bool, error) {
   187  				esList, err := cs.DiscoveryV1().EndpointSlices(f.Namespace.Name).List(ctx, metav1.ListOptions{
   188  					LabelSelector: discoveryv1.LabelServiceName + "=" + svc.Name,
   189  				})
   190  				if err != nil {
   191  					return false, err
   192  				}
   193  				if len(esList.Items) != 0 {
   194  					framework.Logf("Waiting for 0 EndpointSlices to exist, got %d", len(esList.Items))
   195  					return false, nil
   196  				}
   197  
   198  				return true, nil
   199  			}); err != nil {
   200  				framework.Failf("Did not find matching EndpointSlice for %s/%s: %s", svc.Namespace, svc.Name, err)
   201  			}
   202  		})
   203  	})
   204  
   205  	ginkgo.It("should mirror a custom Endpoint with multiple subsets and same IP address", func(ctx context.Context) {
   206  		ns := f.Namespace.Name
   207  		svc := createServiceReportErr(ctx, cs, f.Namespace.Name, &v1.Service{
   208  			ObjectMeta: metav1.ObjectMeta{
   209  				Name: "example-custom-endpoints",
   210  			},
   211  			Spec: v1.ServiceSpec{
   212  				Ports: []v1.ServicePort{
   213  					{
   214  						Name:     "port80",
   215  						Port:     80,
   216  						Protocol: v1.ProtocolTCP,
   217  					},
   218  					{
   219  						Name:     "port81",
   220  						Port:     81,
   221  						Protocol: v1.ProtocolTCP,
   222  					},
   223  				},
   224  			},
   225  		})
   226  
   227  		// Add a backend pod to the service in the other node
   228  		port8080 := []v1.ContainerPort{
   229  			{
   230  				ContainerPort: 8090,
   231  				Protocol:      v1.ProtocolTCP,
   232  			},
   233  		}
   234  		port9090 := []v1.ContainerPort{
   235  			{
   236  				ContainerPort: 9090,
   237  				Protocol:      v1.ProtocolTCP,
   238  			},
   239  		}
   240  
   241  		serverPod := e2epod.NewAgnhostPodFromContainers(
   242  			"", "pod-handle-http-request", nil,
   243  			e2epod.NewAgnhostContainer("container-handle-8090-request", nil, port8080, "netexec", "--http-port", "8090", "--udp-port", "-1"),
   244  			e2epod.NewAgnhostContainer("container-handle-9090-request", nil, port9090, "netexec", "--http-port", "9090", "--udp-port", "-1"),
   245  		)
   246  
   247  		pod := e2epod.NewPodClient(f).CreateSync(ctx, serverPod)
   248  
   249  		if pod.Status.PodIP == "" {
   250  			framework.Failf("PodIP not assigned for pod %s", pod.Name)
   251  		}
   252  
   253  		// create custom endpoints
   254  		endpoints := &v1.Endpoints{
   255  			ObjectMeta: metav1.ObjectMeta{
   256  				Name: svc.Name,
   257  			},
   258  			Subsets: []v1.EndpointSubset{
   259  				{
   260  					Ports: []v1.EndpointPort{{
   261  						Name: "port80",
   262  						Port: 8090,
   263  					}},
   264  					Addresses: []v1.EndpointAddress{{
   265  						IP: pod.Status.PodIP,
   266  					}},
   267  				},
   268  				{
   269  					Ports: []v1.EndpointPort{{
   270  						Name: "port81",
   271  						Port: 9090,
   272  					}},
   273  					Addresses: []v1.EndpointAddress{{
   274  						IP: pod.Status.PodIP,
   275  					}},
   276  				},
   277  			},
   278  		}
   279  
   280  		ginkgo.By("mirroring a new custom Endpoint", func() {
   281  			_, err := cs.CoreV1().Endpoints(f.Namespace.Name).Create(context.TODO(), endpoints, metav1.CreateOptions{})
   282  			framework.ExpectNoError(err, "Unexpected error creating Endpoints")
   283  
   284  			if err := wait.PollImmediate(2*time.Second, 12*time.Second, func() (bool, error) {
   285  				esList, err := cs.DiscoveryV1().EndpointSlices(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{
   286  					LabelSelector: discoveryv1.LabelServiceName + "=" + svc.Name,
   287  				})
   288  				if err != nil {
   289  					framework.Logf("Error listing EndpointSlices: %v", err)
   290  					return false, nil
   291  				}
   292  				if len(esList.Items) == 0 {
   293  					framework.Logf("Waiting for at least 1 EndpointSlice to exist, got %d", len(esList.Items))
   294  					return false, nil
   295  				}
   296  				return true, nil
   297  			}); err != nil {
   298  				framework.Failf("Did not find matching EndpointSlice for %s/%s: %s", svc.Namespace, svc.Name, err)
   299  			}
   300  		})
   301  
   302  		// connect to the service must work
   303  		ginkgo.By("Creating a pause pods that will try to connect to the webservers")
   304  		pausePod := e2epod.NewAgnhostPod(ns, "pause-pod-0", nil, nil, nil)
   305  		e2epod.NewPodClient(f).CreateSync(ctx, pausePod)
   306  		dest1 := net.JoinHostPort(svc.Spec.ClusterIP, "80")
   307  		dest2 := net.JoinHostPort(svc.Spec.ClusterIP, "81")
   308  		execHostnameTest(*pausePod, dest1, pod.Name)
   309  		execHostnameTest(*pausePod, dest2, pod.Name)
   310  
   311  		// delete custom endpoints and wait until the endpoint slices are deleted too
   312  		ginkgo.By("mirroring deletion of a custom Endpoint", func() {
   313  			err := cs.CoreV1().Endpoints(f.Namespace.Name).Delete(context.TODO(), endpoints.Name, metav1.DeleteOptions{})
   314  			framework.ExpectNoError(err, "Unexpected error deleting Endpoints")
   315  
   316  			// Expect mirrored EndpointSlice resource to be updated.
   317  			if err := wait.PollImmediate(2*time.Second, 12*time.Second, func() (bool, error) {
   318  				esList, err := cs.DiscoveryV1().EndpointSlices(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{
   319  					LabelSelector: discoveryv1.LabelServiceName + "=" + svc.Name,
   320  				})
   321  				if err != nil {
   322  					return false, err
   323  				}
   324  				if len(esList.Items) != 0 {
   325  					framework.Logf("Waiting for 0 EndpointSlices to exist, got %d", len(esList.Items))
   326  					return false, nil
   327  				}
   328  
   329  				return true, nil
   330  			}); err != nil {
   331  				framework.Failf("Did not find matching EndpointSlice for %s/%s: %s", svc.Namespace, svc.Name, err)
   332  			}
   333  		})
   334  	})
   335  })