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  }