k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/network/util.go (about) 1 /* 2 Copyright 2014 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 "bytes" 21 "context" 22 "fmt" 23 "net" 24 "regexp" 25 "strings" 26 "time" 27 28 "github.com/onsi/ginkgo/v2" 29 "github.com/onsi/gomega" 30 31 v1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/util/intstr" 34 "k8s.io/apimachinery/pkg/util/wait" 35 "k8s.io/kubernetes/test/e2e/framework" 36 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" 37 e2enetwork "k8s.io/kubernetes/test/e2e/framework/network" 38 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 39 "k8s.io/kubernetes/test/e2e/storage/utils" 40 ) 41 42 // secondNodePortSvcName is the name of the secondary node port service 43 const secondNodePortSvcName = "second-node-port-service" 44 45 // GetHTTPContent returns the content of the given url by HTTP. 46 func GetHTTPContent(host string, port int, timeout time.Duration, url string) (string, error) { 47 var body bytes.Buffer 48 pollErr := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) { 49 result := e2enetwork.PokeHTTP(host, port, url, nil) 50 if result.Status == e2enetwork.HTTPSuccess { 51 body.Write(result.Body) 52 return true, nil 53 } 54 return false, nil 55 }) 56 if pollErr != nil { 57 framework.Logf("Could not reach HTTP service through %v:%v%v after %v: %v", host, port, url, timeout, pollErr) 58 } 59 return body.String(), pollErr 60 } 61 62 // GetHTTPContentFromTestContainer returns the content of the given url by HTTP via a test container. 63 func GetHTTPContentFromTestContainer(ctx context.Context, config *e2enetwork.NetworkingTestConfig, host string, port int, timeout time.Duration, dialCmd string) (string, error) { 64 var body string 65 pollFn := func() (bool, error) { 66 resp, err := config.GetResponseFromTestContainer(ctx, "http", dialCmd, host, port) 67 if err != nil || len(resp.Errors) > 0 || len(resp.Responses) == 0 { 68 return false, nil 69 } 70 body = resp.Responses[0] 71 return true, nil 72 } 73 if pollErr := wait.PollImmediate(framework.Poll, timeout, pollFn); pollErr != nil { 74 return "", pollErr 75 } 76 return body, nil 77 } 78 79 // DescribeSvc logs the output of kubectl describe svc for the given namespace 80 func DescribeSvc(ns string) { 81 framework.Logf("\nOutput of kubectl describe svc:\n") 82 desc, _ := e2ekubectl.RunKubectl( 83 ns, "describe", "svc", fmt.Sprintf("--namespace=%v", ns)) 84 framework.Logf(desc) 85 } 86 87 // CheckSCTPModuleLoadedOnNodes checks whether any node on the list has the 88 // sctp.ko module loaded 89 // For security reasons, and also to allow clusters to use userspace SCTP implementations, 90 // we require that just creating an SCTP Pod/Service/NetworkPolicy must not do anything 91 // that would cause the sctp kernel module to be loaded. 92 func CheckSCTPModuleLoadedOnNodes(ctx context.Context, f *framework.Framework, nodes *v1.NodeList) bool { 93 hostExec := utils.NewHostExec(f) 94 ginkgo.DeferCleanup(hostExec.Cleanup) 95 re := regexp.MustCompile(`^\s*sctp\s+`) 96 cmd := "lsmod | grep sctp" 97 for _, node := range nodes.Items { 98 framework.Logf("Executing cmd %q on node %v", cmd, node.Name) 99 result, err := hostExec.IssueCommandWithResult(ctx, cmd, &node) 100 if err != nil { 101 framework.Logf("sctp module is not loaded or error occurred while executing command %s on node: %v", cmd, err) 102 } 103 for _, line := range strings.Split(result, "\n") { 104 if found := re.Find([]byte(line)); found != nil { 105 framework.Logf("the sctp module is loaded on node: %v", node.Name) 106 return true 107 } 108 } 109 framework.Logf("the sctp module is not loaded on node: %v", node.Name) 110 } 111 return false 112 } 113 114 // execSourceIPTest executes curl to access "/clientip" endpoint on target address 115 // from given Pod to check if source ip is preserved. 116 func execSourceIPTest(sourcePod v1.Pod, targetAddr string) (string, string) { 117 var ( 118 err error 119 stdout string 120 timeout = 2 * time.Minute 121 ) 122 123 framework.Logf("Waiting up to %v to get response from %s", timeout, targetAddr) 124 cmd := fmt.Sprintf(`curl -q -s --connect-timeout 30 %s/clientip`, targetAddr) 125 for start := time.Now(); time.Since(start) < timeout; time.Sleep(2 * time.Second) { 126 stdout, err = e2eoutput.RunHostCmd(sourcePod.Namespace, sourcePod.Name, cmd) 127 if err != nil { 128 framework.Logf("got err: %v, retry until timeout", err) 129 continue 130 } 131 // Need to check output because it might omit in case of error. 132 if strings.TrimSpace(stdout) == "" { 133 framework.Logf("got empty stdout, retry until timeout") 134 continue 135 } 136 break 137 } 138 139 framework.ExpectNoError(err) 140 141 // The stdout return from RunHostCmd is in this format: x.x.x.x:port or [xx:xx:xx::x]:port 142 host, _, err := net.SplitHostPort(stdout) 143 if err != nil { 144 // ginkgo.Fail the test if output format is unexpected. 145 framework.Failf("exec pod returned unexpected stdout: [%v]\n", stdout) 146 } 147 return sourcePod.Status.PodIP, host 148 } 149 150 // execHostnameTest executes curl to access "/hostname" endpoint on target address 151 // from given Pod to check the hostname of the target destination. 152 // It also converts FQDNs to hostnames, so if an FQDN is passed as 153 // targetHostname only the hostname part will be considered for comparison. 154 func execHostnameTest(sourcePod v1.Pod, targetAddr, targetHostname string) { 155 var ( 156 err error 157 stdout string 158 timeout = 2 * time.Minute 159 ) 160 161 framework.Logf("Waiting up to %v to get response from %s", timeout, targetAddr) 162 cmd := fmt.Sprintf(`curl -q -s --max-time 30 %s/hostname`, targetAddr) 163 for start := time.Now(); time.Since(start) < timeout; time.Sleep(2 * time.Second) { 164 stdout, err = e2eoutput.RunHostCmd(sourcePod.Namespace, sourcePod.Name, cmd) 165 if err != nil { 166 framework.Logf("got err: %v, retry until timeout", err) 167 continue 168 } 169 // Need to check output because it might omit in case of error. 170 if strings.TrimSpace(stdout) == "" { 171 framework.Logf("got empty stdout, retry until timeout") 172 continue 173 } 174 break 175 } 176 177 // Ensure we're comparing hostnames and not FQDNs 178 targetHostname = strings.Split(targetHostname, ".")[0] 179 hostname := strings.TrimSpace(strings.Split(stdout, ".")[0]) 180 181 framework.ExpectNoError(err) 182 gomega.Expect(hostname).To(gomega.Equal(targetHostname)) 183 } 184 185 // createSecondNodePortService creates a service with the same selector as config.NodePortService and same HTTP Port 186 func createSecondNodePortService(ctx context.Context, f *framework.Framework, config *e2enetwork.NetworkingTestConfig) (*v1.Service, int) { 187 svc := &v1.Service{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Name: secondNodePortSvcName, 190 }, 191 Spec: v1.ServiceSpec{ 192 Type: v1.ServiceTypeNodePort, 193 Ports: []v1.ServicePort{ 194 { 195 Port: e2enetwork.ClusterHTTPPort, 196 Name: "http", 197 Protocol: v1.ProtocolTCP, 198 TargetPort: intstr.FromInt32(e2enetwork.EndpointHTTPPort), 199 }, 200 }, 201 Selector: config.NodePortService.Spec.Selector, 202 }, 203 } 204 205 createdService := config.CreateService(ctx, svc) 206 207 err := framework.WaitForServiceEndpointsNum(ctx, f.ClientSet, config.Namespace, secondNodePortSvcName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout) 208 framework.ExpectNoError(err, "failed to validate endpoints for service %s in namespace: %s", secondNodePortSvcName, config.Namespace) 209 210 var httpPort int 211 for _, p := range createdService.Spec.Ports { 212 switch p.Protocol { 213 case v1.ProtocolTCP: 214 httpPort = int(p.NodePort) 215 default: 216 continue 217 } 218 } 219 220 return createdService, httpPort 221 } 222 223 // testEndpointReachability tests reachability to endpoints (i.e. IP, ServiceName) and ports. Test request is initiated from specified execPod. 224 // TCP and UDP protocol based service are supported at this moment 225 func testEndpointReachability(ctx context.Context, endpoint string, port int32, protocol v1.Protocol, execPod *v1.Pod, timeout time.Duration) error { 226 cmd := "" 227 switch protocol { 228 case v1.ProtocolTCP: 229 cmd = fmt.Sprintf("echo hostName | nc -v -t -w 2 %s %v", endpoint, port) 230 case v1.ProtocolUDP: 231 cmd = fmt.Sprintf("echo hostName | nc -v -u -w 2 %s %v", endpoint, port) 232 default: 233 return fmt.Errorf("service reachability check is not supported for %v", protocol) 234 } 235 236 err := wait.PollUntilContextTimeout(ctx, framework.Poll, timeout, true, func(ctx context.Context) (bool, error) { 237 stdout, err := e2eoutput.RunHostCmd(execPod.Namespace, execPod.Name, cmd) 238 if err != nil { 239 framework.Logf("Service reachability failing with error: %v\nRetrying...", err) 240 return false, nil 241 } 242 trimmed := strings.TrimSpace(stdout) 243 if trimmed != "" { 244 return true, nil 245 } 246 return false, nil 247 }) 248 if err != nil { 249 return fmt.Errorf("service is not reachable within %v timeout on endpoint %s %d over %s protocol", timeout, endpoint, port, protocol) 250 } 251 return nil 252 }