k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/network/proxy.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 // OWNER = sig/network 18 19 package network 20 21 import ( 22 "bytes" 23 "context" 24 "encoding/json" 25 "fmt" 26 "math" 27 "net/http" 28 "strings" 29 "sync" 30 "time" 31 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 "k8s.io/apimachinery/pkg/util/net" 37 "k8s.io/apimachinery/pkg/util/wait" 38 clientset "k8s.io/client-go/kubernetes" 39 "k8s.io/client-go/transport" 40 "k8s.io/kubernetes/test/e2e/framework" 41 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 42 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 43 e2erc "k8s.io/kubernetes/test/e2e/framework/rc" 44 "k8s.io/kubernetes/test/e2e/network/common" 45 testutils "k8s.io/kubernetes/test/utils" 46 imageutils "k8s.io/kubernetes/test/utils/image" 47 admissionapi "k8s.io/pod-security-admission/api" 48 49 "github.com/onsi/ginkgo/v2" 50 "github.com/onsi/gomega" 51 ) 52 53 const ( 54 // Try all the proxy tests this many times (to catch even rare flakes). 55 proxyAttempts = 20 56 // Only print this many characters of the response (to keep the logs 57 // legible). 58 maxDisplayBodyLen = 100 59 60 // We have seen one of these calls take just over 15 seconds, so putting this at 30. 61 proxyHTTPCallTimeout = 30 * time.Second 62 63 requestRetryPeriod = 10 * time.Millisecond 64 requestRetryTimeout = 1 * time.Minute 65 ) 66 67 type jsonResponse struct { 68 Method string 69 Body string 70 } 71 72 var _ = common.SIGDescribe("Proxy", func() { 73 version := "v1" 74 ginkgo.Context("version "+version, func() { 75 options := framework.Options{ 76 ClientQPS: -1.0, 77 } 78 f := framework.NewFramework("proxy", options, nil) 79 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 80 prefix := "/api/" + version 81 82 /* 83 Test for Proxy, logs port endpoint 84 Select any node in the cluster to invoke /proxy/nodes/<nodeip>:10250/logs endpoint. This endpoint MUST be reachable. 85 */ 86 ginkgo.It("should proxy logs on node with explicit kubelet port using proxy subresource ", func(ctx context.Context) { nodeProxyTest(ctx, f, prefix+"/nodes/", ":10250/proxy/logs/") }) 87 88 /* 89 Test for Proxy, logs endpoint 90 Select any node in the cluster to invoke /proxy/nodes/<nodeip>//logs endpoint. This endpoint MUST be reachable. 91 */ 92 ginkgo.It("should proxy logs on node using proxy subresource ", func(ctx context.Context) { nodeProxyTest(ctx, f, prefix+"/nodes/", "/proxy/logs/") }) 93 94 // using the porter image to serve content, access the content 95 // (of multiple pods?) from multiple (endpoints/services?) 96 /* 97 Release: v1.9 98 Testname: Proxy, logs service endpoint 99 Description: Select any node in the cluster to invoke /logs endpoint using the /nodes/proxy subresource from the kubelet port. This endpoint MUST be reachable. 100 */ 101 framework.ConformanceIt("should proxy through a service and a pod", func(ctx context.Context) { 102 start := time.Now() 103 labels := map[string]string{"proxy-service-target": "true"} 104 service, err := f.ClientSet.CoreV1().Services(f.Namespace.Name).Create(ctx, &v1.Service{ 105 ObjectMeta: metav1.ObjectMeta{ 106 GenerateName: "proxy-service-", 107 }, 108 Spec: v1.ServiceSpec{ 109 Selector: labels, 110 Ports: []v1.ServicePort{ 111 { 112 Name: "portname1", 113 Port: 80, 114 TargetPort: intstr.FromString("dest1"), 115 }, 116 { 117 Name: "portname2", 118 Port: 81, 119 TargetPort: intstr.FromInt32(162), 120 }, 121 { 122 Name: "tlsportname1", 123 Port: 443, 124 TargetPort: intstr.FromString("tlsdest1"), 125 }, 126 { 127 Name: "tlsportname2", 128 Port: 444, 129 TargetPort: intstr.FromInt32(462), 130 }, 131 }, 132 }, 133 }, metav1.CreateOptions{}) 134 framework.ExpectNoError(err) 135 136 // Make an RC with a single pod. The 'porter' image is 137 // a simple server which serves the values of the 138 // environmental variables below. 139 ginkgo.By("starting an echo server on multiple ports") 140 pods := []*v1.Pod{} 141 cfg := testutils.RCConfig{ 142 Client: f.ClientSet, 143 Image: imageutils.GetE2EImage(imageutils.Agnhost), 144 Command: []string{"/agnhost", "porter"}, 145 Name: service.Name, 146 Namespace: f.Namespace.Name, 147 Replicas: 1, 148 PollInterval: time.Second, 149 Env: map[string]string{ 150 "SERVE_PORT_80": `<a href="/rewriteme">test</a>`, 151 "SERVE_PORT_1080": `<a href="/rewriteme">test</a>`, 152 "SERVE_PORT_160": "foo", 153 "SERVE_PORT_162": "bar", 154 155 "SERVE_TLS_PORT_443": `<a href="/tlsrewriteme">test</a>`, 156 "SERVE_TLS_PORT_460": `tls baz`, 157 "SERVE_TLS_PORT_462": `tls qux`, 158 }, 159 Ports: map[string]int{ 160 "dest1": 160, 161 "dest2": 162, 162 163 "tlsdest1": 460, 164 "tlsdest2": 462, 165 }, 166 ReadinessProbe: &v1.Probe{ 167 ProbeHandler: v1.ProbeHandler{ 168 HTTPGet: &v1.HTTPGetAction{ 169 Port: intstr.FromInt32(80), 170 }, 171 }, 172 InitialDelaySeconds: 1, 173 TimeoutSeconds: 5, 174 PeriodSeconds: 10, 175 }, 176 Labels: labels, 177 CreatedPods: &pods, 178 } 179 err = e2erc.RunRC(ctx, cfg) 180 framework.ExpectNoError(err) 181 ginkgo.DeferCleanup(e2erc.DeleteRCAndWaitForGC, f.ClientSet, f.Namespace.Name, cfg.Name) 182 183 err = waitForEndpoint(ctx, f.ClientSet, f.Namespace.Name, service.Name) 184 framework.ExpectNoError(err) 185 186 // table constructors 187 // Try proxying through the service and directly to through the pod. 188 subresourceServiceProxyURL := func(scheme, port string) string { 189 return prefix + "/namespaces/" + f.Namespace.Name + "/services/" + net.JoinSchemeNamePort(scheme, service.Name, port) + "/proxy" 190 } 191 subresourcePodProxyURL := func(scheme, port string) string { 192 return prefix + "/namespaces/" + f.Namespace.Name + "/pods/" + net.JoinSchemeNamePort(scheme, pods[0].Name, port) + "/proxy" 193 } 194 195 // construct the table 196 expectations := map[string]string{ 197 subresourceServiceProxyURL("", "portname1") + "/": "foo", 198 subresourceServiceProxyURL("http", "portname1") + "/": "foo", 199 subresourceServiceProxyURL("", "portname2") + "/": "bar", 200 subresourceServiceProxyURL("http", "portname2") + "/": "bar", 201 subresourceServiceProxyURL("https", "tlsportname1") + "/": "tls baz", 202 subresourceServiceProxyURL("https", "tlsportname2") + "/": "tls qux", 203 204 subresourcePodProxyURL("", "") + "/": `<a href="` + subresourcePodProxyURL("", "") + `/rewriteme">test</a>`, 205 subresourcePodProxyURL("", "1080") + "/": `<a href="` + subresourcePodProxyURL("", "1080") + `/rewriteme">test</a>`, 206 subresourcePodProxyURL("http", "1080") + "/": `<a href="` + subresourcePodProxyURL("http", "1080") + `/rewriteme">test</a>`, 207 subresourcePodProxyURL("", "160") + "/": "foo", 208 subresourcePodProxyURL("http", "160") + "/": "foo", 209 subresourcePodProxyURL("", "162") + "/": "bar", 210 subresourcePodProxyURL("http", "162") + "/": "bar", 211 212 subresourcePodProxyURL("https", "443") + "/": `<a href="` + subresourcePodProxyURL("https", "443") + `/tlsrewriteme">test</a>`, 213 subresourcePodProxyURL("https", "460") + "/": "tls baz", 214 subresourcePodProxyURL("https", "462") + "/": "tls qux", 215 216 // TODO: below entries don't work, but I believe we should make them work. 217 // podPrefix + ":dest1": "foo", 218 // podPrefix + ":dest2": "bar", 219 } 220 221 wg := sync.WaitGroup{} 222 errs := []string{} 223 errLock := sync.Mutex{} 224 recordError := func(s string) { 225 errLock.Lock() 226 defer errLock.Unlock() 227 errs = append(errs, s) 228 } 229 d := time.Since(start) 230 framework.Logf("setup took %v, starting test cases", d) 231 numberTestCases := len(expectations) 232 totalAttempts := numberTestCases * proxyAttempts 233 ginkgo.By(fmt.Sprintf("running %v cases, %v attempts per case, %v total attempts", numberTestCases, proxyAttempts, totalAttempts)) 234 235 for i := 0; i < proxyAttempts; i++ { 236 wg.Add(numberTestCases) 237 for path, val := range expectations { 238 go func(i int, path, val string) { 239 defer wg.Done() 240 // this runs the test case 241 body, status, d, err := doProxy(ctx, f, path, i) 242 243 if err != nil { 244 if serr, ok := err.(*apierrors.StatusError); ok { 245 recordError(fmt.Sprintf("%v (%v; %v): path %v gave status error: %+v", 246 i, status, d, path, serr.Status())) 247 } else { 248 recordError(fmt.Sprintf("%v: path %v gave error: %v", i, path, err)) 249 } 250 return 251 } 252 if status != http.StatusOK { 253 recordError(fmt.Sprintf("%v: path %v gave status: %v", i, path, status)) 254 } 255 if e, a := val, string(body); e != a { 256 recordError(fmt.Sprintf("%v: path %v: wanted %v, got %v", i, path, e, a)) 257 } 258 if d > proxyHTTPCallTimeout { 259 recordError(fmt.Sprintf("%v: path %v took %v > %v", i, path, d, proxyHTTPCallTimeout)) 260 } 261 }(i, path, val) 262 } 263 wg.Wait() 264 } 265 266 if len(errs) != 0 { 267 body, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).GetLogs(pods[0].Name, &v1.PodLogOptions{}).Do(ctx).Raw() 268 if err != nil { 269 framework.Logf("Error getting logs for pod %s: %v", pods[0].Name, err) 270 } else { 271 framework.Logf("Pod %s has the following error logs: %s", pods[0].Name, body) 272 } 273 274 framework.Failf(strings.Join(errs, "\n")) 275 } 276 }) 277 278 /* 279 Release: v1.21 280 Testname: Proxy, validate ProxyWithPath responses 281 Description: Attempt to create a pod and a service. A 282 set of pod and service endpoints MUST be accessed via 283 ProxyWithPath using a list of http methods. A valid 284 response MUST be returned for each endpoint. 285 */ 286 framework.ConformanceIt("A set of valid responses are returned for both pod and service ProxyWithPath", func(ctx context.Context) { 287 288 ns := f.Namespace.Name 289 msg := "foo" 290 testSvcName := "test-service" 291 testSvcLabels := map[string]string{"test": "response"} 292 293 framework.Logf("Creating pod...") 294 pod := &v1.Pod{ 295 ObjectMeta: metav1.ObjectMeta{ 296 Name: "agnhost", 297 Namespace: ns, 298 Labels: map[string]string{ 299 "test": "response"}, 300 }, 301 Spec: v1.PodSpec{ 302 Containers: []v1.Container{{ 303 Image: imageutils.GetE2EImage(imageutils.Agnhost), 304 Name: "agnhost", 305 Command: []string{"/agnhost", "porter", "--json-response"}, 306 Env: []v1.EnvVar{{ 307 Name: "SERVE_PORT_80", 308 Value: msg, 309 }}, 310 }}, 311 RestartPolicy: v1.RestartPolicyNever, 312 }} 313 _, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}) 314 framework.ExpectNoError(err, "failed to create pod") 315 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod), "Pod didn't start within time out period") 316 317 framework.Logf("Creating service...") 318 _, err = f.ClientSet.CoreV1().Services(ns).Create(ctx, &v1.Service{ 319 ObjectMeta: metav1.ObjectMeta{ 320 Name: testSvcName, 321 Namespace: ns, 322 Labels: testSvcLabels, 323 }, 324 Spec: v1.ServiceSpec{ 325 Ports: []v1.ServicePort{{ 326 Port: 80, 327 TargetPort: intstr.FromInt32(80), 328 Protocol: v1.ProtocolTCP, 329 }}, 330 Selector: map[string]string{ 331 "test": "response", 332 }, 333 }}, metav1.CreateOptions{}) 334 framework.ExpectNoError(err, "Failed to create the service") 335 336 transportCfg, err := f.ClientConfig().TransportConfig() 337 framework.ExpectNoError(err, "Error creating transportCfg") 338 restTransport, err := transport.New(transportCfg) 339 framework.ExpectNoError(err, "Error creating restTransport") 340 341 client := &http.Client{ 342 CheckRedirect: func(req *http.Request, via []*http.Request) error { 343 return http.ErrUseLastResponse 344 }, 345 Transport: restTransport, 346 } 347 348 // All methods for Pod ProxyWithPath return 200 349 // For all methods other than HEAD the response body returns 'foo' with the received http method 350 httpVerbs := []string{"DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"} 351 for _, httpVerb := range httpVerbs { 352 353 urlString := strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/pods/agnhost/proxy/some/path/with/" + httpVerb 354 framework.Logf("Starting http.Client for %s", urlString) 355 356 pollErr := wait.PollImmediate(requestRetryPeriod, requestRetryTimeout, validateProxyVerbRequest(client, urlString, httpVerb, msg)) 357 framework.ExpectNoError(err, "Service didn't start within time out period. %v", pollErr) 358 } 359 360 // All methods for Service ProxyWithPath return 200 361 // For all methods other than HEAD the response body returns 'foo' with the received http method 362 for _, httpVerb := range httpVerbs { 363 364 urlString := strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/services/test-service/proxy/some/path/with/" + httpVerb 365 framework.Logf("Starting http.Client for %s", urlString) 366 367 pollErr := wait.PollImmediate(requestRetryPeriod, requestRetryTimeout, validateProxyVerbRequest(client, urlString, httpVerb, msg)) 368 framework.ExpectNoError(err, "Service didn't start within time out period. %v", pollErr) 369 } 370 }) 371 372 /* 373 Release: v1.24 374 Testname: Proxy, validate Proxy responses 375 Description: Attempt to create a pod and a service. A 376 set of pod and service endpoints MUST be accessed via 377 Proxy using a list of http methods. A valid response 378 MUST be returned for each endpoint. 379 */ 380 framework.ConformanceIt("A set of valid responses are returned for both pod and service Proxy", func(ctx context.Context) { 381 382 ns := f.Namespace.Name 383 msg := "foo" 384 testSvcName := "e2e-proxy-test-service" 385 testSvcLabels := map[string]string{"e2e-test": "proxy-endpoints"} 386 387 framework.Logf("Creating pod...") 388 pod := &v1.Pod{ 389 ObjectMeta: metav1.ObjectMeta{ 390 Name: "agnhost", 391 Namespace: ns, 392 Labels: map[string]string{ 393 "e2e-test": "proxy-endpoints"}, 394 }, 395 Spec: v1.PodSpec{ 396 Containers: []v1.Container{{ 397 Image: imageutils.GetE2EImage(imageutils.Agnhost), 398 Name: "agnhost", 399 Command: []string{"/agnhost", "porter", "--json-response"}, 400 Env: []v1.EnvVar{{ 401 Name: "SERVE_PORT_80", 402 Value: msg, 403 }}, 404 }}, 405 RestartPolicy: v1.RestartPolicyNever, 406 }} 407 _, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}) 408 framework.ExpectNoError(err, "failed to create pod") 409 framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod), "Pod didn't start within time out period") 410 411 framework.Logf("Creating service...") 412 _, err = f.ClientSet.CoreV1().Services(ns).Create(ctx, &v1.Service{ 413 ObjectMeta: metav1.ObjectMeta{ 414 Name: testSvcName, 415 Namespace: ns, 416 Labels: testSvcLabels, 417 }, 418 Spec: v1.ServiceSpec{ 419 Ports: []v1.ServicePort{{ 420 Port: 80, 421 TargetPort: intstr.FromInt32(80), 422 Protocol: v1.ProtocolTCP, 423 }}, 424 Selector: map[string]string{ 425 "e2e-test": "proxy-endpoints", 426 }, 427 }}, metav1.CreateOptions{}) 428 framework.ExpectNoError(err, "Failed to create the service") 429 430 transportCfg, err := f.ClientConfig().TransportConfig() 431 framework.ExpectNoError(err, "Error creating transportCfg") 432 restTransport, err := transport.New(transportCfg) 433 framework.ExpectNoError(err, "Error creating restTransport") 434 435 client := &http.Client{ 436 CheckRedirect: func(req *http.Request, via []*http.Request) error { 437 return http.ErrUseLastResponse 438 }, 439 Transport: restTransport, 440 } 441 442 // All methods for Pod Proxy return 200 443 // The response body returns 'foo' with the received http method 444 httpVerbs := []string{"DELETE", "OPTIONS", "PATCH", "POST", "PUT"} 445 for _, httpVerb := range httpVerbs { 446 447 urlString := strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/pods/agnhost/proxy?method=" + httpVerb 448 framework.Logf("Starting http.Client for %s", urlString) 449 450 pollErr := wait.PollImmediate(requestRetryPeriod, requestRetryTimeout, validateProxyVerbRequest(client, urlString, httpVerb, msg)) 451 framework.ExpectNoError(pollErr, "Pod didn't start within time out period. %v", pollErr) 452 } 453 454 // All methods for Service Proxy return 200 455 // The response body returns 'foo' with the received http method 456 for _, httpVerb := range httpVerbs { 457 458 urlString := strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/services/" + testSvcName + "/proxy?method=" + httpVerb 459 framework.Logf("Starting http.Client for %s", urlString) 460 461 pollErr := wait.PollImmediate(requestRetryPeriod, requestRetryTimeout, validateProxyVerbRequest(client, urlString, httpVerb, msg)) 462 framework.ExpectNoError(pollErr, "Service didn't start within time out period. %v", pollErr) 463 } 464 465 // Test that each method returns 301 for both pod and service endpoints 466 redirectVerbs := []string{"GET", "HEAD"} 467 for _, redirectVerb := range redirectVerbs { 468 urlString := strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/pods/agnhost/proxy?method=" + redirectVerb 469 validateRedirectRequest(client, redirectVerb, urlString) 470 471 urlString = strings.TrimRight(f.ClientConfig().Host, "/") + "/api/v1/namespaces/" + ns + "/services/" + testSvcName + "/proxy?method=" + redirectVerb 472 validateRedirectRequest(client, redirectVerb, urlString) 473 } 474 }) 475 }) 476 }) 477 478 func validateRedirectRequest(client *http.Client, redirectVerb string, urlString string) { 479 framework.Logf("Starting http.Client for %s", urlString) 480 request, err := http.NewRequest(redirectVerb, urlString, nil) 481 framework.ExpectNoError(err, "processing request") 482 483 resp, err := client.Do(request) 484 framework.ExpectNoError(err, "processing response") 485 defer resp.Body.Close() 486 487 framework.Logf("http.Client request:%s StatusCode:%d", redirectVerb, resp.StatusCode) 488 gomega.Expect(resp.StatusCode).To(gomega.Equal(301), "The resp.StatusCode returned: %d", resp.StatusCode) 489 } 490 491 // validateProxyVerbRequest checks that a http request to a pod 492 // or service was valid for any http verb. Requires agnhost image 493 // with porter --json-response 494 func validateProxyVerbRequest(client *http.Client, urlString string, httpVerb string, msg string) func() (bool, error) { 495 return func() (bool, error) { 496 var err error 497 498 request, err := http.NewRequest(httpVerb, urlString, nil) 499 if err != nil { 500 framework.Logf("Failed to get a new request. %v", err) 501 return false, nil 502 } 503 504 resp, err := client.Do(request) 505 if err != nil { 506 framework.Logf("Failed to get a response. %v", err) 507 return false, nil 508 } 509 defer resp.Body.Close() 510 511 buf := new(bytes.Buffer) 512 buf.ReadFrom(resp.Body) 513 response := buf.String() 514 515 switch httpVerb { 516 case "HEAD": 517 framework.Logf("http.Client request:%s | StatusCode:%d", httpVerb, resp.StatusCode) 518 if resp.StatusCode != 200 { 519 return false, nil 520 } 521 return true, nil 522 default: 523 var jr *jsonResponse 524 err = json.Unmarshal([]byte(response), &jr) 525 if err != nil { 526 framework.Logf("Failed to process jsonResponse. %v", err) 527 return false, nil 528 } 529 530 framework.Logf("http.Client request:%s | StatusCode:%d | Response:%s | Method:%s", httpVerb, resp.StatusCode, jr.Body, jr.Method) 531 if resp.StatusCode != 200 { 532 return false, nil 533 } 534 535 if msg != jr.Body { 536 return false, nil 537 } 538 539 if httpVerb != jr.Method { 540 return false, nil 541 } 542 return true, nil 543 } 544 } 545 } 546 547 func doProxy(ctx context.Context, f *framework.Framework, path string, i int) (body []byte, statusCode int, d time.Duration, err error) { 548 // About all of the proxy accesses in this file: 549 // * AbsPath is used because it preserves the trailing '/'. 550 // * Do().Raw() is used (instead of DoRaw()) because it will turn an 551 // error from apiserver proxy into an actual error, and there is no 552 // chance of the things we are talking to being confused for an error 553 // that apiserver would have emitted. 554 start := time.Now() 555 body, err = f.ClientSet.CoreV1().RESTClient().Get().AbsPath(path).Do(ctx).StatusCode(&statusCode).Raw() 556 d = time.Since(start) 557 if len(body) > 0 { 558 framework.Logf("(%v) %v: %s (%v; %v)", i, path, truncate(body, maxDisplayBodyLen), statusCode, d) 559 } else { 560 framework.Logf("%v: %s (%v; %v)", path, "no body", statusCode, d) 561 } 562 return 563 } 564 565 func truncate(b []byte, maxLen int) []byte { 566 if len(b) <= maxLen-3 { 567 return b 568 } 569 b2 := append([]byte(nil), b[:maxLen-3]...) 570 b2 = append(b2, '.', '.', '.') 571 return b2 572 } 573 574 func nodeProxyTest(ctx context.Context, f *framework.Framework, prefix, nodeDest string) { 575 // TODO: investigate why it doesn't work on master Node. 576 node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet) 577 framework.ExpectNoError(err) 578 579 // TODO: Change it to test whether all requests succeeded when requests 580 // not reaching Kubelet issue is debugged. 581 serviceUnavailableErrors := 0 582 for i := 0; i < proxyAttempts; i++ { 583 _, status, d, err := doProxy(ctx, f, prefix+node.Name+nodeDest, i) 584 if status == http.StatusServiceUnavailable { 585 framework.Logf("ginkgo.Failed proxying node logs due to service unavailable: %v", err) 586 time.Sleep(time.Second) 587 serviceUnavailableErrors++ 588 } else { 589 framework.ExpectNoError(err) 590 gomega.Expect(status).To(gomega.Equal(http.StatusOK)) 591 gomega.Expect(d).To(gomega.BeNumerically("<", proxyHTTPCallTimeout)) 592 } 593 } 594 if serviceUnavailableErrors > 0 { 595 framework.Logf("error: %d requests to proxy node logs failed", serviceUnavailableErrors) 596 } 597 maxFailures := int(math.Floor(0.1 * float64(proxyAttempts))) 598 gomega.Expect(serviceUnavailableErrors).To(gomega.BeNumerically("<", maxFailures)) 599 } 600 601 // waitForEndpoint waits for the specified endpoint to be ready. 602 func waitForEndpoint(ctx context.Context, c clientset.Interface, ns, name string) error { 603 // registerTimeout is how long to wait for an endpoint to be registered. 604 registerTimeout := time.Minute 605 for t := time.Now(); time.Since(t) < registerTimeout; time.Sleep(framework.Poll) { 606 endpoint, err := c.CoreV1().Endpoints(ns).Get(ctx, name, metav1.GetOptions{}) 607 if apierrors.IsNotFound(err) { 608 framework.Logf("Endpoint %s/%s is not ready yet", ns, name) 609 continue 610 } 611 framework.ExpectNoError(err, "Failed to get endpoints for %s/%s", ns, name) 612 if len(endpoint.Subsets) == 0 || len(endpoint.Subsets[0].Addresses) == 0 { 613 framework.Logf("Endpoint %s/%s is not ready yet", ns, name) 614 continue 615 } 616 return nil 617 } 618 return fmt.Errorf("failed to get endpoints for %s/%s", ns, name) 619 }