From 7cb135a888499fe16f1eb1823b9ad09aa0c0eb65 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Tue, 7 Mar 2023 16:09:54 +0000 Subject: [PATCH] e2e network test for multiple protocol services on same port The test creates a Service exposing two protocols on the same port and a backend that replies on both protocols. 1. Test that Service with works for both protocol 2. Update Service to expose only the TCP port 3. Verify that TCP works and UDP does not work 4. Update Service to expose only the UDP port 5. Verify that TCP does not work and UDP does work Change-Id: Ic4f3a6509e332aa5694d20dfc3b223d7063a7871 --- test/e2e/network/service.go | 108 ++++++++++++++++++++++++++++++++++++ test/e2e/network/util.go | 31 +++++++++++ 2 files changed, 139 insertions(+) diff --git a/test/e2e/network/service.go b/test/e2e/network/service.go index 07ba4e106c2..83d51f452ff 100644 --- a/test/e2e/network/service.go +++ b/test/e2e/network/service.go @@ -3618,6 +3618,114 @@ var _ = common.SIGDescribe("Services", func() { framework.Logf("Collection of services has been deleted") }) + + ginkgo.It("should serve endpoints on same port and different protocols", func(ctx context.Context) { + serviceName := "multiprotocol-test" + testLabels := map[string]string{"app": "multiport"} + ns := f.Namespace.Name + containerPort := 80 + + svcTCPport := v1.ServicePort{ + Name: "tcp-port", + Port: 80, + TargetPort: intstr.FromInt(containerPort), + Protocol: v1.ProtocolTCP, + } + svcUDPport := v1.ServicePort{ + Name: "udp-port", + Port: 80, + TargetPort: intstr.FromInt(containerPort), + Protocol: v1.ProtocolUDP, + } + + ginkgo.By("creating service " + serviceName + " in namespace " + ns) + + testService := v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Labels: testLabels, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeClusterIP, + Selector: testLabels, + Ports: []v1.ServicePort{svcTCPport, svcUDPport}, + }, + } + service, err := cs.CoreV1().Services(ns).Create(ctx, &testService, metav1.CreateOptions{}) + framework.ExpectNoError(err, "failed to create Service") + + containerPorts := []v1.ContainerPort{{ + Name: svcTCPport.Name, + ContainerPort: int32(containerPort), + Protocol: v1.ProtocolTCP, + }, { + Name: svcUDPport.Name, + ContainerPort: int32(containerPort), + Protocol: v1.ProtocolUDP, + }} + podname1 := "pod1" + ginkgo.By("creating pod " + podname1 + " in namespace " + ns) + createPodOrFail(ctx, f, ns, podname1, testLabels, containerPorts, "netexec", "--http-port", strconv.Itoa(containerPort), "--udp-port", strconv.Itoa(containerPort)) + validateEndpointsPortsWithProtocolsOrFail(cs, ns, serviceName, fullPortsByPodName{podname1: containerPorts}) + + ginkgo.By("Checking if the Service forwards traffic to the TCP and UDP port") + execPod := e2epod.CreateExecPodOrFail(ctx, cs, ns, "execpod", nil) + err = testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolTCP, execPod, 30*time.Second) + if err != nil { + framework.Failf("Failed to connect to Service TCP port: %v", err) + } + err = testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolUDP, execPod, 30*time.Second) + if err != nil { + framework.Failf("Failed to connect to Service UDP port: %v", err) + } + + ginkgo.By("Checking if the Service forwards traffic to TCP only") + service, err = cs.CoreV1().Services(ns).Get(ctx, serviceName, metav1.GetOptions{}) + if err != nil { + framework.Failf("failed to get Service %q: %v", serviceName, err) + } + service.Spec.Ports = []v1.ServicePort{svcTCPport} + _, err = cs.CoreV1().Services(ns).Update(ctx, service, metav1.UpdateOptions{}) + if err != nil { + framework.Failf("failed to get Service %q: %v", serviceName, err) + } + + // test reachability + err = testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolTCP, execPod, 30*time.Second) + if err != nil { + framework.Failf("Failed to connect to Service TCP port: %v", err) + } + // take into account the NetworkProgrammingLatency + // testEndpointReachability tries 3 times every 3 second + // we retry again during 30 seconds to check if the port stops forwarding + gomega.Eventually(ctx, func() error { + return testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolUDP, execPod, 6*time.Second) + }).WithTimeout(30 * time.Second).WithPolling(5 * time.Second).ShouldNot(gomega.BeNil()) + + ginkgo.By("Checking if the Service forwards traffic to UDP only") + service, err = cs.CoreV1().Services(ns).Get(ctx, serviceName, metav1.GetOptions{}) + if err != nil { + framework.Failf("failed to get Service %q: %v", serviceName, err) + } + service.Spec.Ports = []v1.ServicePort{svcUDPport} + _, err = cs.CoreV1().Services(ns).Update(ctx, service, metav1.UpdateOptions{}) + if err != nil { + framework.Failf("failed to update Service %q: %v", serviceName, err) + } + + // test reachability + err = testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolUDP, execPod, 30*time.Second) + if err != nil { + framework.Failf("Failed to connect to Service UDP port: %v", err) + } + // take into account the NetworkProgrammingLatency + // testEndpointReachability tries 3 times every 3 second + // we retry again during 30 seconds to check if the port stops forwarding + gomega.Eventually(ctx, func() error { + return testEndpointReachability(ctx, service.Spec.ClusterIP, 80, v1.ProtocolTCP, execPod, 6*time.Second) + }).WithTimeout(30 * time.Second).WithPolling(5 * time.Second).ShouldNot(gomega.BeNil()) + }) + /* Release: v1.26 Testname: Service, same ports with different protocols on a Load Balancer Service diff --git a/test/e2e/network/util.go b/test/e2e/network/util.go index 49889a7ad11..489897a6eff 100644 --- a/test/e2e/network/util.go +++ b/test/e2e/network/util.go @@ -218,3 +218,34 @@ func createSecondNodePortService(ctx context.Context, f *framework.Framework, co return createdService, httpPort } + +// testEndpointReachability tests reachability to endpoints (i.e. IP, ServiceName) and ports. Test request is initiated from specified execPod. +// TCP and UDP protocol based service are supported at this moment +func testEndpointReachability(ctx context.Context, endpoint string, port int32, protocol v1.Protocol, execPod *v1.Pod, timeout time.Duration) error { + cmd := "" + switch protocol { + case v1.ProtocolTCP: + cmd = fmt.Sprintf("echo hostName | nc -v -t -w 2 %s %v", endpoint, port) + case v1.ProtocolUDP: + cmd = fmt.Sprintf("echo hostName | nc -v -u -w 2 %s %v", endpoint, port) + default: + return fmt.Errorf("service reachability check is not supported for %v", protocol) + } + + err := wait.PollImmediateWithContext(ctx, framework.Poll, timeout, func(ctx context.Context) (bool, error) { + stdout, err := e2eoutput.RunHostCmd(execPod.Namespace, execPod.Name, cmd) + if err != nil { + framework.Logf("Service reachability failing with error: %v\nRetrying...", err) + return false, nil + } + trimmed := strings.TrimSpace(stdout) + if trimmed != "" { + return true, nil + } + return false, nil + }) + if err != nil { + return fmt.Errorf("service is not reachable within %v timeout on endpoint %s %d over %s protocol", timeout, endpoint, port, protocol) + } + return nil +}