k8s.io/kubernetes@v1.29.3/test/e2e/network/hostport.go (about) 1 /* 2 Copyright 2021 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 "math/rand" 23 "net" 24 "strconv" 25 26 "github.com/onsi/ginkgo/v2" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/kubernetes/test/e2e/framework" 32 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 33 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 34 "k8s.io/kubernetes/test/e2e/network/common" 35 imageutils "k8s.io/kubernetes/test/utils/image" 36 admissionapi "k8s.io/pod-security-admission/api" 37 ) 38 39 var _ = common.SIGDescribe("HostPort", func() { 40 41 f := framework.NewDefaultFramework("hostport") 42 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 43 44 var ( 45 cs clientset.Interface 46 ns string 47 ) 48 49 ginkgo.BeforeEach(func() { 50 cs = f.ClientSet 51 ns = f.Namespace.Name 52 53 }) 54 55 /* 56 Release: v1.16, v1.21 57 Testname: Scheduling, HostPort matching and HostIP and Protocol not-matching 58 Description: Pods with the same HostPort value MUST be able to be scheduled to the same node 59 if the HostIP or Protocol is different. This test is marked LinuxOnly since hostNetwork is not supported on 60 Windows. 61 */ 62 63 framework.ConformanceIt("validates that there is no conflict between pods with same hostPort but different hostIP and protocol [LinuxOnly]", func(ctx context.Context) { 64 65 localhost := "127.0.0.1" 66 family := v1.IPv4Protocol 67 if framework.TestContext.ClusterIsIPv6() { 68 localhost = "::1" 69 family = v1.IPv6Protocol 70 } 71 // Get a node where to schedule the pods 72 nodes, err := e2enode.GetBoundedReadySchedulableNodes(ctx, cs, 1) 73 framework.ExpectNoError(err) 74 if len(nodes.Items) == 0 { 75 framework.Failf("No nodes available") 76 77 } 78 randomNode := &nodes.Items[rand.Intn(len(nodes.Items))] 79 80 ips := e2enode.GetAddressesByTypeAndFamily(randomNode, v1.NodeInternalIP, family) 81 if len(ips) == 0 { 82 framework.Failf("Failed to get NodeIP") 83 } 84 hostIP := ips[0] 85 port := int32(54323) 86 87 // Create pods with the same HostPort 88 ginkgo.By(fmt.Sprintf("Trying to create a pod(pod1) with hostport %v and hostIP %s and expect scheduled", port, localhost)) 89 createHostPortPodOnNode(ctx, f, "pod1", ns, localhost, port, v1.ProtocolTCP, randomNode.Name) 90 91 ginkgo.By(fmt.Sprintf("Trying to create another pod(pod2) with hostport %v but hostIP %s on the node which pod1 resides and expect scheduled", port, hostIP)) 92 createHostPortPodOnNode(ctx, f, "pod2", ns, hostIP, port, v1.ProtocolTCP, randomNode.Name) 93 94 ginkgo.By(fmt.Sprintf("Trying to create a third pod(pod3) with hostport %v, hostIP %s but use UDP protocol on the node which pod2 resides", port, hostIP)) 95 createHostPortPodOnNode(ctx, f, "pod3", ns, hostIP, port, v1.ProtocolUDP, randomNode.Name) 96 97 // check that the port is being actually exposed to each container 98 // create a pod on the host network in the same node 99 hostExecPod := &v1.Pod{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Name: "e2e-host-exec", 102 Namespace: f.Namespace.Name, 103 }, 104 Spec: v1.PodSpec{ 105 HostNetwork: true, 106 NodeName: randomNode.Name, 107 Containers: []v1.Container{ 108 { 109 Name: "e2e-host-exec", 110 Image: imageutils.GetE2EImage(imageutils.Agnhost), 111 }, 112 }, 113 }, 114 } 115 e2epod.NewPodClient(f).CreateSync(ctx, hostExecPod) 116 117 // use a 5 seconds timeout per connection 118 timeout := 5 119 // IPv6 doesn't NAT from localhost -> localhost, it doesn't have the route_localnet kernel hack, so we need to specify the source IP 120 cmdPod1 := []string{"/bin/sh", "-c", fmt.Sprintf("curl -g --connect-timeout %v --interface %s http://%s/hostname", timeout, hostIP, net.JoinHostPort(localhost, strconv.Itoa(int(port))))} 121 cmdPod2 := []string{"/bin/sh", "-c", fmt.Sprintf("curl -g --connect-timeout %v http://%s/hostname", timeout, net.JoinHostPort(hostIP, strconv.Itoa(int(port))))} 122 cmdPod3 := []string{"/bin/sh", "-c", fmt.Sprintf("echo hostname | nc -u -w %v %s %d", timeout, hostIP, port)} 123 // try 5 times to connect to the exposed ports 124 for i := 0; i < 5; i++ { 125 // check pod1 126 ginkgo.By(fmt.Sprintf("checking connectivity from pod %s to serverIP: %s, port: %d", hostExecPod.Name, localhost, port)) 127 hostname1, _, err := e2epod.ExecCommandInContainerWithFullOutput(f, hostExecPod.Name, "e2e-host-exec", cmdPod1...) 128 if err != nil { 129 framework.Logf("Can not connect from %s to pod(pod1) to serverIP: %s, port: %d", hostExecPod.Name, localhost, port) 130 continue 131 } 132 // check pod2 133 ginkgo.By(fmt.Sprintf("checking connectivity from pod %s to serverIP: %s, port: %d", hostExecPod.Name, hostIP, port)) 134 hostname2, _, err := e2epod.ExecCommandInContainerWithFullOutput(f, hostExecPod.Name, "e2e-host-exec", cmdPod2...) 135 if err != nil { 136 framework.Logf("Can not connect from %s to pod(pod2) to serverIP: %s, port: %d", hostExecPod.Name, hostIP, port) 137 continue 138 } 139 // the hostname returned has to be different because we are exposing the same port to two different pods 140 if hostname1 == hostname2 { 141 framework.Logf("pods must have different hostname: pod1 has hostname %s, pod2 has hostname %s", hostname1, hostname2) 142 continue 143 } 144 // check pod3 145 ginkgo.By(fmt.Sprintf("checking connectivity from pod %s to serverIP: %s, port: %d UDP", hostExecPod.Name, hostIP, port)) 146 hostname3, _, err := e2epod.ExecCommandInContainerWithFullOutput(f, hostExecPod.Name, "e2e-host-exec", cmdPod3...) 147 if err != nil { 148 framework.Logf("Can not connect from %s to pod(pod2) to serverIP: %s, port: %d", hostExecPod.Name, hostIP, port) 149 continue 150 } 151 if hostname1 == hostname3 { 152 framework.Logf("pods must have different hostname: pod1 has hostname %s, pod3 has hostname %s", hostname1, hostname3) 153 continue 154 } 155 if hostname2 == hostname3 { 156 framework.Logf("pods must have different hostname: pod2 has hostname %s, pod3 has hostname %s", hostname2, hostname3) 157 continue 158 } 159 return 160 } 161 framework.Failf("Failed to connect to exposed host ports") 162 }) 163 }) 164 165 // create pod which using hostport on the specified node according to the nodeSelector 166 // it starts an http server on the exposed port 167 func createHostPortPodOnNode(ctx context.Context, f *framework.Framework, podName, ns, hostIP string, port int32, protocol v1.Protocol, nodeName string) { 168 169 var netexecArgs []string 170 var readinessProbePort int32 171 172 if protocol == v1.ProtocolTCP { 173 readinessProbePort = 8080 174 netexecArgs = []string{"--http-port=8080", "--udp-port=-1"} 175 } else { 176 readinessProbePort = 8008 177 netexecArgs = []string{"--http-port=8008", "--udp-port=8080"} 178 } 179 180 hostPortPod := &v1.Pod{ 181 ObjectMeta: metav1.ObjectMeta{ 182 Name: podName, 183 }, 184 Spec: v1.PodSpec{ 185 Containers: []v1.Container{ 186 { 187 Name: "agnhost", 188 Image: imageutils.GetE2EImage(imageutils.Agnhost), 189 Args: append([]string{"netexec"}, netexecArgs...), 190 Ports: []v1.ContainerPort{ 191 { 192 HostPort: port, 193 ContainerPort: 8080, 194 Protocol: protocol, 195 HostIP: hostIP, 196 }, 197 }, 198 ReadinessProbe: &v1.Probe{ 199 ProbeHandler: v1.ProbeHandler{ 200 HTTPGet: &v1.HTTPGetAction{ 201 Path: "/hostname", 202 Port: intstr.IntOrString{ 203 IntVal: readinessProbePort, 204 }, 205 Scheme: v1.URISchemeHTTP, 206 }, 207 }, 208 }, 209 }, 210 }, 211 NodeName: nodeName, 212 }, 213 } 214 if _, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, hostPortPod, metav1.CreateOptions{}); err != nil { 215 framework.Failf("error creating pod %s, err:%v", podName, err) 216 } 217 218 if err := e2epod.WaitTimeoutForPodReadyInNamespace(ctx, f.ClientSet, podName, ns, framework.PodStartTimeout); err != nil { 219 framework.Failf("wait for pod %s timeout, err:%v", podName, err) 220 } 221 }