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