k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/network/utils.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 "context" 21 "crypto/tls" 22 "encoding/json" 23 "fmt" 24 "io" 25 "net" 26 "net/http" 27 "strconv" 28 "strings" 29 "time" 30 31 "github.com/onsi/ginkgo/v2" 32 v1 "k8s.io/api/core/v1" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/util/intstr" 36 utilnet "k8s.io/apimachinery/pkg/util/net" 37 "k8s.io/apimachinery/pkg/util/sets" 38 "k8s.io/apimachinery/pkg/util/uuid" 39 "k8s.io/apimachinery/pkg/util/wait" 40 clientset "k8s.io/client-go/kubernetes" 41 coreclientset "k8s.io/client-go/kubernetes/typed/core/v1" 42 "k8s.io/kubernetes/test/e2e/framework" 43 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" 44 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 45 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 46 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 47 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 48 e2essh "k8s.io/kubernetes/test/e2e/framework/ssh" 49 storageutils "k8s.io/kubernetes/test/e2e/storage/utils" 50 imageutils "k8s.io/kubernetes/test/utils/image" 51 netutils "k8s.io/utils/net" 52 ) 53 54 const ( 55 // EndpointHTTPPort is an endpoint HTTP port for testing. 56 EndpointHTTPPort = 8083 57 // EndpointUDPPort is an endpoint UDP port for testing. 58 EndpointUDPPort = 8081 59 // EndpointSCTPPort is an endpoint SCTP port for testing. 60 EndpointSCTPPort = 8082 61 // testContainerHTTPPort is the test container http port. 62 testContainerHTTPPort = 9080 63 // ClusterHTTPPort is a cluster HTTP port for testing. 64 ClusterHTTPPort = 80 65 // ClusterUDPPort is a cluster UDP port for testing. 66 ClusterUDPPort = 90 67 // ClusterSCTPPort is a cluster SCTP port for testing. 68 ClusterSCTPPort = 95 69 testPodName = "test-container-pod" 70 hostTestPodName = "host-test-container-pod" 71 nodePortServiceName = "node-port-service" 72 sessionAffinityServiceName = "session-affinity-service" 73 // wait time between poll attempts of a Service vip and/or nodePort. 74 // coupled with testTries to produce a net timeout value. 75 hitEndpointRetryDelay = 2 * time.Second 76 // Number of retries to hit a given set of endpoints. Needs to be high 77 // because we verify iptables statistical rr loadbalancing. 78 testTries = 30 79 // Maximum number of pods in a test, to make test work in large clusters. 80 maxNetProxyPodsCount = 10 81 // SessionAffinityChecks is number of checks to hit a given set of endpoints when enable session affinity. 82 SessionAffinityChecks = 10 83 // RegexIPv4 is a regex to match IPv4 addresses 84 RegexIPv4 = "(?:\\d+)\\.(?:\\d+)\\.(?:\\d+)\\.(?:\\d+)" 85 // RegexIPv6 is a regex to match IPv6 addresses 86 RegexIPv6 = "(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))" 87 resizeNodeReadyTimeout = 2 * time.Minute 88 resizeNodeNotReadyTimeout = 2 * time.Minute 89 // netexec dial commands 90 // the destination will echo its hostname. 91 echoHostname = "hostname" 92 ) 93 94 // Option is used to configure the NetworkingTest object 95 type Option func(*NetworkingTestConfig) 96 97 // EnableSCTP listen on SCTP ports on the endpoints 98 func EnableSCTP(config *NetworkingTestConfig) { 99 config.SCTPEnabled = true 100 } 101 102 // EnableDualStack create Dual Stack services 103 func EnableDualStack(config *NetworkingTestConfig) { 104 config.DualStackEnabled = true 105 } 106 107 // UseHostNetwork run the test container with HostNetwork=true. 108 func UseHostNetwork(config *NetworkingTestConfig) { 109 config.HostNetwork = true 110 } 111 112 // EndpointsUseHostNetwork run the endpoints pods with HostNetwork=true. 113 func EndpointsUseHostNetwork(config *NetworkingTestConfig) { 114 config.EndpointsHostNetwork = true 115 } 116 117 // PreferExternalAddresses prefer node External Addresses for the tests 118 func PreferExternalAddresses(config *NetworkingTestConfig) { 119 config.PreferExternalAddresses = true 120 } 121 122 // NewNetworkingTestConfig creates and sets up a new test config helper. 123 func NewNetworkingTestConfig(ctx context.Context, f *framework.Framework, setters ...Option) *NetworkingTestConfig { 124 // default options 125 config := &NetworkingTestConfig{ 126 f: f, 127 Namespace: f.Namespace.Name, 128 } 129 for _, setter := range setters { 130 setter(config) 131 } 132 ginkgo.By(fmt.Sprintf("Performing setup for networking test in namespace %v", config.Namespace)) 133 config.setup(ctx, getServiceSelector()) 134 return config 135 } 136 137 // NewCoreNetworkingTestConfig creates and sets up a new test config helper for Node E2E. 138 func NewCoreNetworkingTestConfig(ctx context.Context, f *framework.Framework, hostNetwork bool) *NetworkingTestConfig { 139 // default options 140 config := &NetworkingTestConfig{ 141 f: f, 142 Namespace: f.Namespace.Name, 143 HostNetwork: hostNetwork, 144 } 145 ginkgo.By(fmt.Sprintf("Performing setup for networking test in namespace %v", config.Namespace)) 146 config.setupCore(ctx, getServiceSelector()) 147 return config 148 } 149 150 func getServiceSelector() map[string]string { 151 ginkgo.By("creating a selector") 152 selectorName := "selector-" + string(uuid.NewUUID()) 153 serviceSelector := map[string]string{ 154 selectorName: "true", 155 } 156 return serviceSelector 157 } 158 159 // NetworkingTestConfig is a convenience class around some utility methods 160 // for testing kubeproxy/networking/services/endpoints. 161 type NetworkingTestConfig struct { 162 // TestContainerPod is a test pod running the netexec image. It is capable 163 // of executing tcp/udp requests against ip:port. 164 TestContainerPod *v1.Pod 165 // HostTestContainerPod is a pod running using the hostexec image. 166 HostTestContainerPod *v1.Pod 167 // if the HostTestContainerPod is running with HostNetwork=true. 168 HostNetwork bool 169 // if the endpoints Pods are running with HostNetwork=true. 170 EndpointsHostNetwork bool 171 // if the test pods are listening on sctp port. We need this as sctp tests 172 // are marked as disruptive as they may load the sctp module. 173 SCTPEnabled bool 174 // DualStackEnabled enables dual stack on services 175 DualStackEnabled bool 176 // EndpointPods are the pods belonging to the Service created by this 177 // test config. Each invocation of `setup` creates a service with 178 // 1 pod per node running the netexecImage. 179 EndpointPods []*v1.Pod 180 f *framework.Framework 181 podClient *e2epod.PodClient 182 // NodePortService is a Service with Type=NodePort spanning over all 183 // endpointPods. 184 NodePortService *v1.Service 185 // SessionAffinityService is a Service with SessionAffinity=ClientIP 186 // spanning over all endpointPods. 187 SessionAffinityService *v1.Service 188 // Nodes is a list of nodes in the cluster. 189 Nodes []v1.Node 190 // MaxTries is the number of retries tolerated for tests run against 191 // endpoints and services created by this config. 192 MaxTries int 193 // The ClusterIP of the Service created by this test config. 194 ClusterIP string 195 // The SecondaryClusterIP of the Service created by this test config. 196 SecondaryClusterIP string 197 // NodeIP it's an ExternalIP if the node has one, 198 // or an InternalIP if not, for use in nodePort testing. 199 NodeIP string 200 // SecondaryNodeIP it's an ExternalIP of the secondary IP family if the node has one, 201 // or an InternalIP if not, for usein nodePort testing. 202 SecondaryNodeIP string 203 // The http/udp/sctp nodePorts of the Service. 204 NodeHTTPPort int 205 NodeUDPPort int 206 NodeSCTPPort int 207 // The kubernetes namespace within which all resources for this 208 // config are created 209 Namespace string 210 // Whether to prefer node External Addresses for the tests 211 PreferExternalAddresses bool 212 } 213 214 // NetexecDialResponse represents the response returned by the `netexec` subcommand of `agnhost` 215 type NetexecDialResponse struct { 216 Responses []string `json:"responses"` 217 Errors []string `json:"errors"` 218 } 219 220 // DialFromEndpointContainer executes a curl via kubectl exec in an endpoint container. Returns an error to be handled by the caller. 221 func (config *NetworkingTestConfig) DialFromEndpointContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error { 222 return config.DialFromContainer(ctx, protocol, echoHostname, config.EndpointPods[0].Status.PodIP, targetIP, EndpointHTTPPort, targetPort, maxTries, minTries, expectedEps) 223 } 224 225 // DialFromTestContainer executes a curl via kubectl exec in a test container. Returns an error to be handled by the caller. 226 func (config *NetworkingTestConfig) DialFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error { 227 return config.DialFromContainer(ctx, protocol, echoHostname, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedEps) 228 } 229 230 // DialEchoFromTestContainer executes a curl via kubectl exec in a test container. The response is expected to match the echoMessage, Returns an error to be handled by the caller. 231 func (config *NetworkingTestConfig) DialEchoFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, echoMessage string) error { 232 expectedResponse := sets.NewString() 233 expectedResponse.Insert(echoMessage) 234 var dialCommand string 235 236 // NOTE(claudiub): netexec /dialCommand will send a request to the given targetIP and targetPort as follows: 237 // for HTTP: it will send a request to: http://targetIP:targetPort/dialCommand 238 // for UDP: it will send targetCommand as a message. The consumer receives the data message and looks for 239 // a few starting strings, including echo, and treats it accordingly. 240 if protocol == "http" { 241 dialCommand = fmt.Sprintf("echo?msg=%s", echoMessage) 242 } else { 243 dialCommand = fmt.Sprintf("echo%%20%s", echoMessage) 244 } 245 return config.DialFromContainer(ctx, protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedResponse) 246 } 247 248 // diagnoseMissingEndpoints prints debug information about the endpoints that 249 // are NOT in the given list of foundEndpoints. These are the endpoints we 250 // expected a response from. 251 func (config *NetworkingTestConfig) diagnoseMissingEndpoints(foundEndpoints sets.String) { 252 for _, e := range config.EndpointPods { 253 if foundEndpoints.Has(e.Name) { 254 continue 255 } 256 framework.Logf("\nOutput of kubectl describe pod %v/%v:\n", e.Namespace, e.Name) 257 desc, _ := e2ekubectl.RunKubectl( 258 e.Namespace, "describe", "pod", e.Name, fmt.Sprintf("--namespace=%v", e.Namespace)) 259 framework.Logf(desc) 260 } 261 } 262 263 // EndpointHostnames returns a set of hostnames for existing endpoints. 264 func (config *NetworkingTestConfig) EndpointHostnames() sets.String { 265 expectedEps := sets.NewString() 266 for _, p := range config.EndpointPods { 267 if config.EndpointsHostNetwork { 268 expectedEps.Insert(p.Spec.NodeSelector["kubernetes.io/hostname"]) 269 } else { 270 expectedEps.Insert(p.Name) 271 } 272 } 273 return expectedEps 274 } 275 276 func makeCURLDialCommand(ipPort, dialCmd, protocol, targetIP string, targetPort int) string { 277 // The current versions of curl included in CentOS and RHEL distros 278 // misinterpret square brackets around IPv6 as globbing, so use the -g 279 // argument to disable globbing to handle the IPv6 case. 280 return fmt.Sprintf("curl -g -q -s 'http://%s/dial?request=%s&protocol=%s&host=%s&port=%d&tries=1'", 281 ipPort, 282 dialCmd, 283 protocol, 284 targetIP, 285 targetPort) 286 } 287 288 // DialFromContainer executes a curl via kubectl exec in a test container, 289 // which might then translate to a tcp or udp request based on the protocol 290 // argument in the url. 291 // - minTries is the minimum number of curl attempts required before declaring 292 // success. Set to 0 if you'd like to return as soon as all endpoints respond 293 // at least once. 294 // - maxTries is the maximum number of curl attempts. If this many attempts pass 295 // and we don't see all expected endpoints, the test fails. 296 // - targetIP is the source Pod IP that will dial the given dialCommand using the given protocol. 297 // - dialCommand is the command that the targetIP will send to the targetIP using the given protocol. 298 // the dialCommand should be formatted properly for the protocol (http: URL path+parameters, 299 // udp: command%20parameters, where parameters are optional) 300 // - expectedResponses is the unordered set of responses to wait for. The responses are based on 301 // the dialCommand; for example, for the dialCommand "hostname", the expectedResponses 302 // should contain the hostnames reported by each pod in the service through /hostName. 303 // 304 // maxTries == minTries will confirm that we see the expected endpoints and no 305 // more for maxTries. Use this if you want to eg: fail a readiness check on a 306 // pod and confirm it doesn't show up as an endpoint. 307 // Returns nil if no error, or error message if failed after trying maxTries. 308 func (config *NetworkingTestConfig) DialFromContainer(ctx context.Context, protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) error { 309 ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort)) 310 cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort) 311 312 responses := sets.NewString() 313 314 for i := 0; i < maxTries; i++ { 315 resp, err := config.GetResponseFromContainer(ctx, protocol, dialCommand, containerIP, targetIP, containerHTTPPort, targetPort) 316 if err != nil { 317 // A failure to kubectl exec counts as a try, not a hard fail. 318 // Also note that we will keep failing for maxTries in tests where 319 // we confirm unreachability. 320 framework.Logf("GetResponseFromContainer: %s", err) 321 continue 322 } 323 for _, response := range resp.Responses { 324 trimmed := strings.TrimSpace(response) 325 if trimmed != "" { 326 responses.Insert(trimmed) 327 } 328 } 329 if responses.Difference(expectedResponses).Len() > 0 { 330 returnMsg := fmt.Errorf("received unexpected responses... \nAttempt %d\nCommand %v\nretrieved %v\nexpected %v", i, cmd, responses, expectedResponses) 331 // TODO(aojea) Remove once issues.k8s.io/123760 is solved 332 // Dump the nodes network routes and addresses for troubleshooting #123760 333 framework.Logf("encountered error during dial (%v)", returnMsg) 334 hostExec := storageutils.NewHostExec(config.f) 335 ginkgo.DeferCleanup(hostExec.Cleanup) 336 cmd := `echo "IP routes: " && ip route && echo "IP addresses:" && ip addr && echo "Open sockets: " && ss -anp --socket=tcp` 337 for _, node := range config.Nodes { 338 result, err := hostExec.IssueCommandWithResult(ctx, cmd, &node) 339 if err != nil { 340 framework.Logf("error occurred while executing command %s on node: %v", cmd, err) 341 continue 342 } 343 framework.Logf("Dump network information for node %s:\n%s", node.Name, result) 344 } 345 // Dump the node iptables rules and conntrack flows for troubleshooting #123760 346 podList, _ := config.f.ClientSet.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{ 347 LabelSelector: "k8s-app=kube-proxy", 348 }) 349 for _, pod := range podList.Items { 350 // dump only for the node running test-container-pod 351 if pod.Status.HostIP == config.TestContainerPod.Status.HostIP { 352 output, _, _ := e2epod.ExecWithOptions(config.f, e2epod.ExecOptions{ 353 Namespace: "kube-system", 354 PodName: pod.Name, 355 ContainerName: "kube-proxy", 356 Command: []string{"sh", "-c", fmt.Sprintf(`echo "IPTables Dump: " && iptables-save | grep "%s/%s:http" && echo "Conntrack flows: " && conntrack -Ln -p tcp | grep %d`, config.Namespace, config.NodePortService.Name, EndpointHTTPPort)}, 357 Stdin: nil, 358 CaptureStdout: true, 359 CaptureStderr: true, 360 PreserveWhitespace: false, 361 }) 362 framework.Logf("Dump iptables and connntrack flows\n%s", output) 363 break 364 } 365 } 366 return returnMsg 367 } 368 369 framework.Logf("Waiting for responses: %v", expectedResponses.Difference(responses)) 370 371 // Check against i+1 so we exit if minTries == maxTries. 372 if (responses.Equal(expectedResponses) || responses.Len() == 0 && expectedResponses.Len() == 0) && i+1 >= minTries { 373 framework.Logf("reached %v after %v/%v tries", targetIP, i, maxTries) 374 return nil 375 } 376 // TODO: get rid of this delay #36281 377 time.Sleep(hitEndpointRetryDelay) 378 } 379 if dialCommand == echoHostname { 380 config.diagnoseMissingEndpoints(responses) 381 } 382 returnMsg := fmt.Errorf("did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, responses, expectedResponses) 383 framework.Logf("encountered error during dial (%v)", returnMsg) 384 return returnMsg 385 386 } 387 388 // GetEndpointsFromTestContainer executes a curl via kubectl exec in a test container. 389 func (config *NetworkingTestConfig) GetEndpointsFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, tries int) (sets.String, error) { 390 return config.GetEndpointsFromContainer(ctx, protocol, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, tries) 391 } 392 393 // GetEndpointsFromContainer executes a curl via kubectl exec in a test container, 394 // which might then translate to a tcp or udp request based on the protocol argument 395 // in the url. It returns all different endpoints from multiple retries. 396 // - tries is the number of curl attempts. If this many attempts pass and 397 // we don't see any endpoints, the test fails. 398 func (config *NetworkingTestConfig) GetEndpointsFromContainer(ctx context.Context, protocol, containerIP, targetIP string, containerHTTPPort, targetPort, tries int) (sets.String, error) { 399 ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort)) 400 cmd := makeCURLDialCommand(ipPort, "hostName", protocol, targetIP, targetPort) 401 402 eps := sets.NewString() 403 404 for i := 0; i < tries; i++ { 405 stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd) 406 if err != nil { 407 // A failure to kubectl exec counts as a try, not a hard fail. 408 // Also note that we will keep failing for maxTries in tests where 409 // we confirm unreachability. 410 framework.Logf("Failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr) 411 } else { 412 podInfo := fmt.Sprintf("name: %v, namespace: %v, hostIp: %v, podIp: %v, conditions: %v", config.TestContainerPod.Name, config.TestContainerPod.Namespace, config.TestContainerPod.Status.HostIP, config.TestContainerPod.Status.PodIP, config.TestContainerPod.Status.Conditions) 413 framework.Logf("Tries: %d, in try: %d, stdout: %v, stderr: %v, command run in Pod { %#v }", tries, i, stdout, stderr, podInfo) 414 415 var output NetexecDialResponse 416 if err := json.Unmarshal([]byte(stdout), &output); err != nil { 417 framework.Logf("WARNING: Failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v", 418 cmd, config.TestContainerPod.Name, stdout, err) 419 continue 420 } 421 422 for _, hostName := range output.Responses { 423 trimmed := strings.TrimSpace(hostName) 424 if trimmed != "" { 425 eps.Insert(trimmed) 426 } 427 } 428 // TODO: get rid of this delay #36281 429 time.Sleep(hitEndpointRetryDelay) 430 } 431 } 432 return eps, nil 433 } 434 435 // GetResponseFromContainer executes a curl via kubectl exec in a container. 436 func (config *NetworkingTestConfig) GetResponseFromContainer(ctx context.Context, protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort int) (NetexecDialResponse, error) { 437 ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort)) 438 cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort) 439 440 stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd) 441 if err != nil { 442 return NetexecDialResponse{}, fmt.Errorf("failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr) 443 } 444 445 var output NetexecDialResponse 446 if err := json.Unmarshal([]byte(stdout), &output); err != nil { 447 return NetexecDialResponse{}, fmt.Errorf("failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v", 448 cmd, config.TestContainerPod.Name, stdout, err) 449 } 450 return output, nil 451 } 452 453 // GetResponseFromTestContainer executes a curl via kubectl exec in a test container. 454 func (config *NetworkingTestConfig) GetResponseFromTestContainer(ctx context.Context, protocol, dialCommand, targetIP string, targetPort int) (NetexecDialResponse, error) { 455 return config.GetResponseFromContainer(ctx, protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort) 456 } 457 458 // GetHTTPCodeFromTestContainer executes a curl via kubectl exec in a test container and returns the status code. 459 func (config *NetworkingTestConfig) GetHTTPCodeFromTestContainer(ctx context.Context, path, targetIP string, targetPort int) (int, error) { 460 cmd := fmt.Sprintf("curl -g -q -s -o /dev/null -w %%{http_code} http://%s:%d%s", 461 targetIP, 462 targetPort, 463 path) 464 stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd) 465 // We only care about the status code reported by curl, 466 // and want to return any other errors, such as cannot execute command in the Pod. 467 // If curl failed to connect to host, it would exit with code 7, which makes `ExecShellInPodWithFullOutput` 468 // return a non-nil error and output "000" to stdout. 469 if err != nil && len(stdout) == 0 { 470 return 0, fmt.Errorf("failed to execute %q: %v, stderr: %q", cmd, err, stderr) 471 } 472 code, err := strconv.Atoi(stdout) 473 if err != nil { 474 return 0, fmt.Errorf("failed to parse status code returned by healthz endpoint: %w, code: %s", err, stdout) 475 } 476 return code, nil 477 } 478 479 // DialFromNode executes a tcp/udp curl/nc request based on protocol via kubectl exec 480 // in a test container running with host networking. 481 // - minTries is the minimum number of curl/nc attempts required before declaring 482 // success. If 0, then we return as soon as all endpoints succeed. 483 // - There is no logical change to test results if faillures happen AFTER endpoints have succeeded, 484 // hence over-padding minTries will NOT reverse a successful result and is thus not very useful yet 485 // (See the TODO about checking probability, which isn't implemented yet). 486 // - maxTries is the maximum number of curl/echo attempts before an error is returned. The 487 // smaller this number is, the less 'slack' there is for declaring success. 488 // - if maxTries < expectedEps, this test is guaranteed to return an error, because all endpoints won't be hit. 489 // - maxTries == minTries will return as soon as all endpoints succeed (or fail once maxTries is reached without 490 // success on all endpoints). 491 // In general its prudent to have a high enough level of minTries to guarantee that all pods get a fair chance at receiving traffic. 492 func (config *NetworkingTestConfig) DialFromNode(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error { 493 var cmd string 494 if protocol == "udp" { 495 cmd = fmt.Sprintf("echo hostName | nc -w 1 -u %s %d", targetIP, targetPort) 496 } else { 497 ipPort := net.JoinHostPort(targetIP, strconv.Itoa(targetPort)) 498 // The current versions of curl included in CentOS and RHEL distros 499 // misinterpret square brackets around IPv6 as globbing, so use the -g 500 // argument to disable globbing to handle the IPv6 case. 501 cmd = fmt.Sprintf("curl -g -q -s --max-time 15 --connect-timeout 1 http://%s/hostName", ipPort) 502 } 503 504 // TODO: This simply tells us that we can reach the endpoints. Check that 505 // the probability of hitting a specific endpoint is roughly the same as 506 // hitting any other. 507 eps := sets.NewString() 508 509 filterCmd := fmt.Sprintf("%s | grep -v '^\\s*$'", cmd) 510 framework.Logf("Going to poll %v on port %v at least %v times, with a maximum of %v tries before failing", targetIP, targetPort, minTries, maxTries) 511 for i := 0; i < maxTries; i++ { 512 stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.HostTestContainerPod.Name, filterCmd) 513 if err != nil || len(stderr) > 0 { 514 // A failure to exec command counts as a try, not a hard fail. 515 // Also note that we will keep failing for maxTries in tests where 516 // we confirm unreachability. 517 framework.Logf("Failed to execute %q: %v, stdout: %q, stderr: %q", filterCmd, err, stdout, stderr) 518 } else { 519 trimmed := strings.TrimSpace(stdout) 520 if trimmed != "" { 521 eps.Insert(trimmed) 522 } 523 } 524 525 // Check against i+1 so we exit if minTries == maxTries. 526 if eps.Equal(expectedEps) && i+1 >= minTries { 527 framework.Logf("Found all %d expected endpoints: %+v", eps.Len(), eps.List()) 528 return nil 529 } 530 531 framework.Logf("Waiting for %+v endpoints (expected=%+v, actual=%+v)", expectedEps.Difference(eps).List(), expectedEps.List(), eps.List()) 532 533 // TODO: get rid of this delay #36281 534 time.Sleep(hitEndpointRetryDelay) 535 } 536 537 config.diagnoseMissingEndpoints(eps) 538 return fmt.Errorf("failed to find expected endpoints, \ntries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, eps, expectedEps) 539 } 540 541 // GetSelfURL executes a curl against the given path via kubectl exec into a 542 // test container running with host networking, and fails if the output 543 // doesn't match the expected string. 544 func (config *NetworkingTestConfig) GetSelfURL(ctx context.Context, port int32, path string, expected string) { 545 cmd := fmt.Sprintf("curl -i -q -s --connect-timeout 1 http://localhost:%d%s", port, path) 546 ginkgo.By(fmt.Sprintf("Getting kube-proxy self URL %s", path)) 547 config.executeCurlCmd(ctx, cmd, expected) 548 } 549 550 // GetSelfURLStatusCode executes a curl against the given path via kubectl exec into a 551 // test container running with host networking, and fails if the returned status 552 // code doesn't match the expected string. 553 func (config *NetworkingTestConfig) GetSelfURLStatusCode(ctx context.Context, port int32, path string, expected string) { 554 // check status code 555 cmd := fmt.Sprintf("curl -o /dev/null -i -q -s -w %%{http_code} --connect-timeout 1 http://localhost:%d%s", port, path) 556 ginkgo.By(fmt.Sprintf("Checking status code against http://localhost:%d%s", port, path)) 557 config.executeCurlCmd(ctx, cmd, expected) 558 } 559 560 func (config *NetworkingTestConfig) executeCurlCmd(ctx context.Context, cmd string, expected string) { 561 // These are arbitrary timeouts. The curl command should pass on first try, 562 // unless remote server is starved/bootstrapping/restarting etc. 563 const retryInterval = 1 * time.Second 564 const retryTimeout = 30 * time.Second 565 podName := config.HostTestContainerPod.Name 566 var msg string 567 if pollErr := wait.PollUntilContextTimeout(ctx, retryInterval, retryTimeout, true, func(ctx context.Context) (bool, error) { 568 stdout, err := e2epodoutput.RunHostCmd(config.Namespace, podName, cmd) 569 if err != nil { 570 msg = fmt.Sprintf("failed executing cmd %v in %v/%v: %v", cmd, config.Namespace, podName, err) 571 framework.Logf(msg) 572 return false, nil 573 } 574 if !strings.Contains(stdout, expected) { 575 msg = fmt.Sprintf("successfully executed %v in %v/%v, but output '%v' doesn't contain expected string '%v'", cmd, config.Namespace, podName, stdout, expected) 576 framework.Logf(msg) 577 return false, nil 578 } 579 return true, nil 580 }); pollErr != nil { 581 framework.Logf("\nOutput of kubectl describe pod %v/%v:\n", config.Namespace, podName) 582 desc, _ := e2ekubectl.RunKubectl( 583 config.Namespace, "describe", "pod", podName, fmt.Sprintf("--namespace=%v", config.Namespace)) 584 framework.Logf("%s", desc) 585 framework.Failf("Timed out in %v: %v", retryTimeout, msg) 586 } 587 } 588 589 func (config *NetworkingTestConfig) createNetShellPodSpec(podName, hostname string) *v1.Pod { 590 netexecArgs := []string{ 591 "netexec", 592 fmt.Sprintf("--http-port=%d", EndpointHTTPPort), 593 fmt.Sprintf("--udp-port=%d", EndpointUDPPort), 594 } 595 // In case of hostnetwork endpoints, we want to bind the udp listener to specific ip addresses. 596 // In order to cover legacy AND dualstack, we pass both the host ip and the two pod ips. Agnhost 597 // removes duplicates and so this will listen on both addresses (or on the single existing one). 598 if config.EndpointsHostNetwork { 599 netexecArgs = append(netexecArgs, "--udp-listen-addresses=$(HOST_IP),$(POD_IPS)") 600 } 601 602 probe := &v1.Probe{ 603 InitialDelaySeconds: 10, 604 TimeoutSeconds: 30, 605 PeriodSeconds: 10, 606 SuccessThreshold: 1, 607 FailureThreshold: 3, 608 ProbeHandler: v1.ProbeHandler{ 609 HTTPGet: &v1.HTTPGetAction{ 610 Path: "/healthz", 611 Port: intstr.IntOrString{IntVal: EndpointHTTPPort}, 612 }, 613 }, 614 } 615 pod := &v1.Pod{ 616 TypeMeta: metav1.TypeMeta{ 617 Kind: "Pod", 618 APIVersion: "v1", 619 }, 620 ObjectMeta: metav1.ObjectMeta{ 621 Name: podName, 622 Namespace: config.Namespace, 623 }, 624 Spec: v1.PodSpec{ 625 Containers: []v1.Container{ 626 { 627 Name: "webserver", 628 Image: imageutils.GetE2EImage(imageutils.Agnhost), 629 ImagePullPolicy: v1.PullIfNotPresent, 630 Args: netexecArgs, 631 Ports: []v1.ContainerPort{ 632 { 633 Name: "http", 634 ContainerPort: EndpointHTTPPort, 635 }, 636 { 637 Name: "udp", 638 ContainerPort: EndpointUDPPort, 639 Protocol: v1.ProtocolUDP, 640 }, 641 }, 642 LivenessProbe: probe, 643 ReadinessProbe: probe, 644 }, 645 }, 646 NodeSelector: map[string]string{ 647 "kubernetes.io/hostname": hostname, 648 }, 649 }, 650 } 651 // we want sctp to be optional as it will load the sctp kernel module 652 if config.SCTPEnabled { 653 pod.Spec.Containers[0].Args = append(pod.Spec.Containers[0].Args, fmt.Sprintf("--sctp-port=%d", EndpointSCTPPort)) 654 pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, v1.ContainerPort{ 655 Name: "sctp", 656 ContainerPort: EndpointSCTPPort, 657 Protocol: v1.ProtocolSCTP, 658 }) 659 } 660 661 if config.EndpointsHostNetwork { 662 pod.Spec.Containers[0].Env = []v1.EnvVar{ 663 { 664 Name: "HOST_IP", 665 ValueFrom: &v1.EnvVarSource{ 666 FieldRef: &v1.ObjectFieldSelector{ 667 FieldPath: "status.hostIP", 668 }, 669 }, 670 }, 671 { 672 Name: "POD_IPS", 673 ValueFrom: &v1.EnvVarSource{ 674 FieldRef: &v1.ObjectFieldSelector{ 675 FieldPath: "status.podIPs", 676 }, 677 }, 678 }, 679 } 680 } 681 return pod 682 } 683 684 func (config *NetworkingTestConfig) createTestPodSpec() *v1.Pod { 685 pod := &v1.Pod{ 686 TypeMeta: metav1.TypeMeta{ 687 Kind: "Pod", 688 APIVersion: "v1", 689 }, 690 ObjectMeta: metav1.ObjectMeta{ 691 Name: testPodName, 692 Namespace: config.Namespace, 693 }, 694 Spec: v1.PodSpec{ 695 Containers: []v1.Container{ 696 { 697 Name: "webserver", 698 Image: imageutils.GetE2EImage(imageutils.Agnhost), 699 ImagePullPolicy: v1.PullIfNotPresent, 700 Args: []string{ 701 "netexec", 702 fmt.Sprintf("--http-port=%d", testContainerHTTPPort), 703 }, 704 Ports: []v1.ContainerPort{ 705 { 706 Name: "http", 707 ContainerPort: testContainerHTTPPort, 708 }, 709 }, 710 }, 711 }, 712 }, 713 } 714 return pod 715 } 716 717 func (config *NetworkingTestConfig) createNodePortServiceSpec(svcName string, selector map[string]string, enableSessionAffinity bool) *v1.Service { 718 sessionAffinity := v1.ServiceAffinityNone 719 if enableSessionAffinity { 720 sessionAffinity = v1.ServiceAffinityClientIP 721 } 722 res := &v1.Service{ 723 ObjectMeta: metav1.ObjectMeta{ 724 Name: svcName, 725 }, 726 Spec: v1.ServiceSpec{ 727 Type: v1.ServiceTypeNodePort, 728 Ports: []v1.ServicePort{ 729 {Port: ClusterHTTPPort, Name: "http", Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt32(EndpointHTTPPort)}, 730 {Port: ClusterUDPPort, Name: "udp", Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt32(EndpointUDPPort)}, 731 }, 732 Selector: selector, 733 SessionAffinity: sessionAffinity, 734 }, 735 } 736 737 if config.SCTPEnabled { 738 res.Spec.Ports = append(res.Spec.Ports, v1.ServicePort{Port: ClusterSCTPPort, Name: "sctp", Protocol: v1.ProtocolSCTP, TargetPort: intstr.FromInt32(EndpointSCTPPort)}) 739 } 740 if config.DualStackEnabled { 741 requireDual := v1.IPFamilyPolicyRequireDualStack 742 res.Spec.IPFamilyPolicy = &requireDual 743 } 744 return res 745 } 746 747 func (config *NetworkingTestConfig) createNodePortService(ctx context.Context, selector map[string]string) { 748 config.NodePortService = config.CreateService(ctx, config.createNodePortServiceSpec(nodePortServiceName, selector, false)) 749 } 750 751 func (config *NetworkingTestConfig) createSessionAffinityService(ctx context.Context, selector map[string]string) { 752 config.SessionAffinityService = config.CreateService(ctx, config.createNodePortServiceSpec(sessionAffinityServiceName, selector, true)) 753 } 754 755 // DeleteNodePortService deletes NodePort service. 756 func (config *NetworkingTestConfig) DeleteNodePortService(ctx context.Context) { 757 err := config.getServiceClient().Delete(ctx, config.NodePortService.Name, metav1.DeleteOptions{}) 758 framework.ExpectNoError(err, "error while deleting NodePortService. err:%v)", err) 759 time.Sleep(15 * time.Second) // wait for kube-proxy to catch up with the service being deleted. 760 } 761 762 func (config *NetworkingTestConfig) createTestPods(ctx context.Context) { 763 testContainerPod := config.createTestPodSpec() 764 hostTestContainerPod := e2epod.NewExecPodSpec(config.Namespace, hostTestPodName, config.HostNetwork) 765 766 config.createPod(ctx, testContainerPod) 767 if config.HostNetwork { 768 config.createPod(ctx, hostTestContainerPod) 769 } 770 771 framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, config.f.ClientSet, testContainerPod.Name, config.f.Namespace.Name)) 772 773 var err error 774 config.TestContainerPod, err = config.getPodClient().Get(ctx, testContainerPod.Name, metav1.GetOptions{}) 775 if err != nil { 776 framework.Failf("Failed to retrieve %s pod: %v", testContainerPod.Name, err) 777 } 778 779 if config.HostNetwork { 780 framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, config.f.ClientSet, hostTestContainerPod.Name, config.f.Namespace.Name)) 781 config.HostTestContainerPod, err = config.getPodClient().Get(ctx, hostTestContainerPod.Name, metav1.GetOptions{}) 782 if err != nil { 783 framework.Failf("Failed to retrieve %s pod: %v", hostTestContainerPod.Name, err) 784 } 785 } 786 } 787 788 // CreateService creates the provided service in config.Namespace and returns created service 789 func (config *NetworkingTestConfig) CreateService(ctx context.Context, serviceSpec *v1.Service) *v1.Service { 790 _, err := config.getServiceClient().Create(ctx, serviceSpec, metav1.CreateOptions{}) 791 framework.ExpectNoError(err, fmt.Sprintf("Failed to create %s service: %v", serviceSpec.Name, err)) 792 793 err = WaitForService(ctx, config.f.ClientSet, config.Namespace, serviceSpec.Name, true, 5*time.Second, 45*time.Second) 794 framework.ExpectNoError(err, fmt.Sprintf("error while waiting for service:%s err: %v", serviceSpec.Name, err)) 795 796 createdService, err := config.getServiceClient().Get(ctx, serviceSpec.Name, metav1.GetOptions{}) 797 framework.ExpectNoError(err, fmt.Sprintf("Failed to create %s service: %v", serviceSpec.Name, err)) 798 799 return createdService 800 } 801 802 // setupCore sets up the pods and core test config 803 // mainly for simplified node e2e setup 804 func (config *NetworkingTestConfig) setupCore(ctx context.Context, selector map[string]string) { 805 ginkgo.By("Creating the service pods in kubernetes") 806 podName := "netserver" 807 config.EndpointPods = config.createNetProxyPods(ctx, podName, selector) 808 809 ginkgo.By("Creating test pods") 810 config.createTestPods(ctx) 811 812 epCount := len(config.EndpointPods) 813 814 // Note that this is not O(n^2) in practice, because epCount SHOULD be < 10. In cases that epCount is > 10, this would be prohibitively large. 815 // Check maxNetProxyPodsCount for details. 816 config.MaxTries = epCount*epCount + testTries 817 framework.Logf("Setting MaxTries for pod polling to %v for networking test based on endpoint count %v", config.MaxTries, epCount) 818 } 819 820 // setup includes setupCore and also sets up services 821 func (config *NetworkingTestConfig) setup(ctx context.Context, selector map[string]string) { 822 config.setupCore(ctx, selector) 823 824 ginkgo.By("Getting node addresses") 825 framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, config.f.ClientSet, 10*time.Minute)) 826 nodeList, err := e2enode.GetReadySchedulableNodes(ctx, config.f.ClientSet) 827 framework.ExpectNoError(err) 828 829 e2eskipper.SkipUnlessNodeCountIsAtLeast(2) 830 config.Nodes = nodeList.Items 831 832 ginkgo.By("Creating the service on top of the pods in kubernetes") 833 config.createNodePortService(ctx, selector) 834 config.createSessionAffinityService(ctx, selector) 835 836 for _, p := range config.NodePortService.Spec.Ports { 837 switch p.Protocol { 838 case v1.ProtocolUDP: 839 config.NodeUDPPort = int(p.NodePort) 840 case v1.ProtocolTCP: 841 config.NodeHTTPPort = int(p.NodePort) 842 case v1.ProtocolSCTP: 843 config.NodeSCTPPort = int(p.NodePort) 844 default: 845 continue 846 } 847 } 848 849 // obtain the ClusterIP 850 config.ClusterIP = config.NodePortService.Spec.ClusterIP 851 if config.DualStackEnabled { 852 config.SecondaryClusterIP = config.NodePortService.Spec.ClusterIPs[1] 853 } 854 855 // Obtain the primary IP family of the Cluster based on the first ClusterIP 856 // TODO: Eventually we should just be getting these from Spec.IPFamilies 857 // but for now that would only if the feature gate is enabled. 858 family := v1.IPv4Protocol 859 secondaryFamily := v1.IPv6Protocol 860 if netutils.IsIPv6String(config.ClusterIP) { 861 family = v1.IPv6Protocol 862 secondaryFamily = v1.IPv4Protocol 863 } 864 if config.PreferExternalAddresses { 865 // Get Node IPs from the cluster, ExternalIPs take precedence 866 config.NodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeExternalIP, family) 867 } 868 if config.NodeIP == "" { 869 config.NodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeInternalIP, family) 870 } 871 if config.DualStackEnabled { 872 if config.PreferExternalAddresses { 873 config.SecondaryNodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeExternalIP, secondaryFamily) 874 } 875 if config.SecondaryNodeIP == "" { 876 config.SecondaryNodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeInternalIP, secondaryFamily) 877 } 878 } 879 880 ginkgo.By("Waiting for NodePort service to expose endpoint") 881 err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, nodePortServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout) 882 framework.ExpectNoError(err, "failed to validate endpoints for service %s in namespace: %s", nodePortServiceName, config.Namespace) 883 ginkgo.By("Waiting for Session Affinity service to expose endpoint") 884 err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, sessionAffinityServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout) 885 framework.ExpectNoError(err, "failed to validate endpoints for service %s in namespace: %s", sessionAffinityServiceName, config.Namespace) 886 } 887 888 func (config *NetworkingTestConfig) createNetProxyPods(ctx context.Context, podName string, selector map[string]string) []*v1.Pod { 889 framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, config.f.ClientSet, 10*time.Minute)) 890 nodeList, err := e2enode.GetBoundedReadySchedulableNodes(ctx, config.f.ClientSet, maxNetProxyPodsCount) 891 framework.ExpectNoError(err) 892 nodes := nodeList.Items 893 894 // create pods, one for each node 895 createdPods := make([]*v1.Pod, 0, len(nodes)) 896 for i, n := range nodes { 897 podName := fmt.Sprintf("%s-%d", podName, i) 898 hostname, _ := n.Labels["kubernetes.io/hostname"] 899 pod := config.createNetShellPodSpec(podName, hostname) 900 pod.ObjectMeta.Labels = selector 901 pod.Spec.HostNetwork = config.EndpointsHostNetwork 902 903 // NOTE(claudiub): In order to use HostNetwork on Windows, we need to use Privileged Containers. 904 if pod.Spec.HostNetwork && framework.NodeOSDistroIs("windows") { 905 e2epod.WithWindowsHostProcess(pod, "") 906 } 907 createdPod := config.createPod(ctx, pod) 908 createdPods = append(createdPods, createdPod) 909 } 910 911 // wait that all of them are up 912 runningPods := make([]*v1.Pod, 0, len(nodes)) 913 for _, p := range createdPods { 914 framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, config.f.ClientSet, p.Name, config.f.Namespace.Name, framework.PodStartTimeout)) 915 rp, err := config.getPodClient().Get(ctx, p.Name, metav1.GetOptions{}) 916 framework.ExpectNoError(err) 917 runningPods = append(runningPods, rp) 918 } 919 920 return runningPods 921 } 922 923 // DeleteNetProxyPod deletes the first endpoint pod and waits for it being removed. 924 func (config *NetworkingTestConfig) DeleteNetProxyPod(ctx context.Context) { 925 pod := config.EndpointPods[0] 926 framework.ExpectNoError(config.getPodClient().Delete(ctx, pod.Name, metav1.DeleteOptions{})) 927 config.EndpointPods = config.EndpointPods[1:] 928 // wait for pod being deleted. 929 err := e2epod.WaitForPodNotFoundInNamespace(ctx, config.f.ClientSet, pod.Name, config.Namespace, config.f.Timeouts.PodDelete) 930 if err != nil { 931 framework.Failf("Failed to delete %s pod: %v", pod.Name, err) 932 } 933 // wait for endpoint being removed. 934 err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, nodePortServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout) 935 if err != nil { 936 framework.Failf("Failed to remove endpoint from service: %s", nodePortServiceName) 937 } 938 // wait for kube-proxy to catch up with the pod being deleted. 939 time.Sleep(5 * time.Second) 940 } 941 942 func (config *NetworkingTestConfig) createPod(ctx context.Context, pod *v1.Pod) *v1.Pod { 943 return config.getPodClient().Create(ctx, pod) 944 } 945 946 func (config *NetworkingTestConfig) getPodClient() *e2epod.PodClient { 947 if config.podClient == nil { 948 config.podClient = e2epod.NewPodClient(config.f) 949 } 950 return config.podClient 951 } 952 953 func (config *NetworkingTestConfig) getServiceClient() coreclientset.ServiceInterface { 954 return config.f.ClientSet.CoreV1().Services(config.Namespace) 955 } 956 957 // HTTPPokeParams is a struct for HTTP poke parameters. 958 type HTTPPokeParams struct { 959 Timeout time.Duration // default = 10 secs 960 ExpectCode int // default = 200 961 BodyContains string 962 RetriableCodes []int 963 EnableHTTPS bool 964 } 965 966 // HTTPPokeResult is a struct for HTTP poke result. 967 type HTTPPokeResult struct { 968 Status HTTPPokeStatus 969 Code int // HTTP code: 0 if the connection was not made 970 Error error // if there was any error 971 Body []byte // if code != 0 972 } 973 974 // HTTPPokeStatus is string for representing HTTP poke status. 975 type HTTPPokeStatus string 976 977 const ( 978 // HTTPSuccess is HTTP poke status which is success. 979 HTTPSuccess HTTPPokeStatus = "Success" 980 // HTTPError is HTTP poke status which is error. 981 HTTPError HTTPPokeStatus = "UnknownError" 982 // HTTPTimeout is HTTP poke status which is timeout. 983 HTTPTimeout HTTPPokeStatus = "TimedOut" 984 // HTTPRefused is HTTP poke status which is connection refused. 985 HTTPRefused HTTPPokeStatus = "ConnectionRefused" 986 // HTTPRetryCode is HTTP poke status which is retry code. 987 HTTPRetryCode HTTPPokeStatus = "RetryCode" 988 // HTTPWrongCode is HTTP poke status which is wrong code. 989 HTTPWrongCode HTTPPokeStatus = "WrongCode" 990 // HTTPBadResponse is HTTP poke status which is bad response. 991 HTTPBadResponse HTTPPokeStatus = "BadResponse" 992 // Any time we add new errors, we should audit all callers of this. 993 ) 994 995 // PokeHTTP tries to connect to a host on a port for a given URL path. Callers 996 // can specify additional success parameters, if desired. 997 // 998 // The result status will be characterized as precisely as possible, given the 999 // known users of this. 1000 // 1001 // The result code will be zero in case of any failure to connect, or non-zero 1002 // if the HTTP transaction completed (even if the other test params make this a 1003 // failure). 1004 // 1005 // The result error will be populated for any status other than Success. 1006 // 1007 // The result body will be populated if the HTTP transaction was completed, even 1008 // if the other test params make this a failure). 1009 func PokeHTTP(host string, port int, path string, params *HTTPPokeParams) HTTPPokeResult { 1010 // Set default params. 1011 if params == nil { 1012 params = &HTTPPokeParams{} 1013 } 1014 1015 hostPort := net.JoinHostPort(host, strconv.Itoa(port)) 1016 var url string 1017 if params.EnableHTTPS { 1018 url = fmt.Sprintf("https://%s%s", hostPort, path) 1019 } else { 1020 url = fmt.Sprintf("http://%s%s", hostPort, path) 1021 } 1022 1023 ret := HTTPPokeResult{} 1024 1025 // Sanity check inputs, because it has happened. These are the only things 1026 // that should hard fail the test - they are basically ASSERT()s. 1027 if host == "" { 1028 framework.Failf("Got empty host for HTTP poke (%s)", url) 1029 return ret 1030 } 1031 if port == 0 { 1032 framework.Failf("Got port==0 for HTTP poke (%s)", url) 1033 return ret 1034 } 1035 1036 if params.ExpectCode == 0 { 1037 params.ExpectCode = http.StatusOK 1038 } 1039 1040 if params.Timeout == 0 { 1041 params.Timeout = 10 * time.Second 1042 } 1043 1044 framework.Logf("Poking %q", url) 1045 1046 resp, err := httpGetNoConnectionPoolTimeout(url, params.Timeout) 1047 if err != nil { 1048 ret.Error = err 1049 neterr, ok := err.(net.Error) 1050 if ok && neterr.Timeout() { 1051 ret.Status = HTTPTimeout 1052 } else if strings.Contains(err.Error(), "connection refused") { 1053 ret.Status = HTTPRefused 1054 } else { 1055 ret.Status = HTTPError 1056 } 1057 framework.Logf("Poke(%q): %v", url, err) 1058 return ret 1059 } 1060 1061 ret.Code = resp.StatusCode 1062 1063 defer resp.Body.Close() 1064 body, err := io.ReadAll(resp.Body) 1065 if err != nil { 1066 ret.Status = HTTPError 1067 ret.Error = fmt.Errorf("error reading HTTP body: %w", err) 1068 framework.Logf("Poke(%q): %v", url, ret.Error) 1069 return ret 1070 } 1071 ret.Body = make([]byte, len(body)) 1072 copy(ret.Body, body) 1073 1074 if resp.StatusCode != params.ExpectCode { 1075 for _, code := range params.RetriableCodes { 1076 if resp.StatusCode == code { 1077 ret.Error = fmt.Errorf("retriable status code: %d", resp.StatusCode) 1078 ret.Status = HTTPRetryCode 1079 framework.Logf("Poke(%q): %v", url, ret.Error) 1080 return ret 1081 } 1082 } 1083 ret.Status = HTTPWrongCode 1084 ret.Error = fmt.Errorf("bad status code: %d", resp.StatusCode) 1085 framework.Logf("Poke(%q): %v", url, ret.Error) 1086 return ret 1087 } 1088 1089 if params.BodyContains != "" && !strings.Contains(string(body), params.BodyContains) { 1090 ret.Status = HTTPBadResponse 1091 ret.Error = fmt.Errorf("response does not contain expected substring: %q", string(body)) 1092 framework.Logf("Poke(%q): %v", url, ret.Error) 1093 return ret 1094 } 1095 1096 ret.Status = HTTPSuccess 1097 framework.Logf("Poke(%q): success", url) 1098 return ret 1099 } 1100 1101 // Does an HTTP GET, but does not reuse TCP connections 1102 // This masks problems where the iptables rule has changed, but we don't see it 1103 func httpGetNoConnectionPoolTimeout(url string, timeout time.Duration) (*http.Response, error) { 1104 tr := utilnet.SetTransportDefaults(&http.Transport{ 1105 DisableKeepAlives: true, 1106 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 1107 }) 1108 client := &http.Client{ 1109 Transport: tr, 1110 Timeout: timeout, 1111 } 1112 1113 return client.Get(url) 1114 } 1115 1116 // TestUnderTemporaryNetworkFailure blocks outgoing network traffic on 'node'. Then runs testFunc and returns its status. 1117 // At the end (even in case of errors), the network traffic is brought back to normal. 1118 // This function executes commands on a node so it will work only for some 1119 // environments. 1120 func TestUnderTemporaryNetworkFailure(ctx context.Context, c clientset.Interface, ns string, node *v1.Node, testFunc func(ctx context.Context)) { 1121 host, err := e2enode.GetSSHExternalIP(node) 1122 if err != nil { 1123 framework.Failf("Error getting node external ip : %v", err) 1124 } 1125 controlPlaneAddresses := framework.GetControlPlaneAddresses(ctx, c) 1126 ginkgo.By(fmt.Sprintf("block network traffic from node %s to the control plane", node.Name)) 1127 defer func() { 1128 // This code will execute even if setting the iptables rule failed. 1129 // It is on purpose because we may have an error even if the new rule 1130 // had been inserted. (yes, we could look at the error code and ssh error 1131 // separately, but I prefer to stay on the safe side). 1132 ginkgo.By(fmt.Sprintf("Unblock network traffic from node %s to the control plane", node.Name)) 1133 for _, instanceAddress := range controlPlaneAddresses { 1134 UnblockNetwork(ctx, host, instanceAddress) 1135 } 1136 }() 1137 1138 framework.Logf("Waiting %v to ensure node %s is ready before beginning test...", resizeNodeReadyTimeout, node.Name) 1139 if !e2enode.WaitConditionToBe(ctx, c, node.Name, v1.NodeReady, true, resizeNodeReadyTimeout) { 1140 framework.Failf("Node %s did not become ready within %v", node.Name, resizeNodeReadyTimeout) 1141 } 1142 for _, instanceAddress := range controlPlaneAddresses { 1143 BlockNetwork(ctx, host, instanceAddress) 1144 } 1145 1146 framework.Logf("Waiting %v for node %s to be not ready after simulated network failure", resizeNodeNotReadyTimeout, node.Name) 1147 if !e2enode.WaitConditionToBe(ctx, c, node.Name, v1.NodeReady, false, resizeNodeNotReadyTimeout) { 1148 framework.Failf("Node %s did not become not-ready within %v", node.Name, resizeNodeNotReadyTimeout) 1149 } 1150 1151 testFunc(ctx) 1152 // network traffic is unblocked in a deferred function 1153 } 1154 1155 // BlockNetwork blocks network between the given from value and the given to value. 1156 // The following helper functions can block/unblock network from source 1157 // host to destination host by manipulating iptable rules. 1158 // This function assumes it can ssh to the source host. 1159 // 1160 // Caution: 1161 // Recommend to input IP instead of hostnames. Using hostnames will cause iptables to 1162 // do a DNS lookup to resolve the name to an IP address, which will 1163 // slow down the test and cause it to fail if DNS is absent or broken. 1164 // 1165 // Suggested usage pattern: 1166 // 1167 // func foo() { 1168 // ... 1169 // defer UnblockNetwork(from, to) 1170 // BlockNetwork(from, to) 1171 // ... 1172 // } 1173 func BlockNetwork(ctx context.Context, from string, to string) { 1174 framework.Logf("block network traffic from %s to %s", from, to) 1175 iptablesRule := fmt.Sprintf("OUTPUT --destination %s --jump REJECT", to) 1176 dropCmd := fmt.Sprintf("sudo iptables --insert %s", iptablesRule) 1177 if result, err := e2essh.SSH(ctx, dropCmd, from, framework.TestContext.Provider); result.Code != 0 || err != nil { 1178 e2essh.LogResult(result) 1179 framework.Failf("Unexpected error: %v", err) 1180 } 1181 } 1182 1183 // UnblockNetwork unblocks network between the given from value and the given to value. 1184 func UnblockNetwork(ctx context.Context, from string, to string) { 1185 framework.Logf("Unblock network traffic from %s to %s", from, to) 1186 iptablesRule := fmt.Sprintf("OUTPUT --destination %s --jump REJECT", to) 1187 undropCmd := fmt.Sprintf("sudo iptables --delete %s", iptablesRule) 1188 // Undrop command may fail if the rule has never been created. 1189 // In such case we just lose 30 seconds, but the cluster is healthy. 1190 // But if the rule had been created and removing it failed, the node is broken and 1191 // not coming back. Subsequent tests will run or fewer nodes (some of the tests 1192 // may fail). Manual intervention is required in such case (recreating the 1193 // cluster solves the problem too). 1194 err := wait.PollWithContext(ctx, time.Millisecond*100, time.Second*30, func(ctx context.Context) (bool, error) { 1195 result, err := e2essh.SSH(ctx, undropCmd, from, framework.TestContext.Provider) 1196 if result.Code == 0 && err == nil { 1197 return true, nil 1198 } 1199 e2essh.LogResult(result) 1200 if err != nil { 1201 framework.Logf("Unexpected error: %v", err) 1202 } 1203 return false, nil 1204 }) 1205 if err != nil { 1206 framework.Failf("Failed to remove the iptable REJECT rule. Manual intervention is "+ 1207 "required on host %s: remove rule %s, if exists", from, iptablesRule) 1208 } 1209 } 1210 1211 // WaitForService waits until the service appears (exist == true), or disappears (exist == false) 1212 func WaitForService(ctx context.Context, c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error { 1213 err := wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (bool, error) { 1214 _, err := c.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{}) 1215 switch { 1216 case err == nil: 1217 framework.Logf("Service %s in namespace %s found.", name, namespace) 1218 return exist, nil 1219 case apierrors.IsNotFound(err): 1220 framework.Logf("Service %s in namespace %s disappeared.", name, namespace) 1221 return !exist, nil 1222 case err != nil: 1223 framework.Logf("Non-retryable failure while getting service.") 1224 return false, err 1225 default: 1226 framework.Logf("Get service %s in namespace %s failed: %v", name, namespace, err) 1227 return false, nil 1228 } 1229 }) 1230 if err != nil { 1231 stateMsg := map[bool]string{true: "to appear", false: "to disappear"} 1232 return fmt.Errorf("error waiting for service %s/%s %s: %w", namespace, name, stateMsg[exist], err) 1233 } 1234 return nil 1235 }