...

Source file src/k8s.io/kubernetes/pkg/probe/http/http_test.go

Documentation: k8s.io/kubernetes/pkg/probe/http

     1  /*
     2  Copyright 2015 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 http
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"net"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"net/url"
    26  	"os"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/kubernetes/pkg/probe"
    37  )
    38  
    39  const FailureCode int = -1
    40  
    41  func unsetEnv(t testing.TB, key string) {
    42  	if originalValue, ok := os.LookupEnv(key); ok {
    43  		t.Cleanup(func() { os.Setenv(key, originalValue) })
    44  		os.Unsetenv(key)
    45  	}
    46  }
    47  
    48  func TestHTTPProbeProxy(t *testing.T) {
    49  	res := "welcome to http probe proxy"
    50  
    51  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    52  		fmt.Fprint(w, res)
    53  	}))
    54  	defer server.Close()
    55  
    56  	localProxy := server.URL
    57  
    58  	t.Setenv("http_proxy", localProxy)
    59  	t.Setenv("HTTP_PROXY", localProxy)
    60  	unsetEnv(t, "no_proxy")
    61  	unsetEnv(t, "NO_PROXY")
    62  
    63  	followNonLocalRedirects := true
    64  	prober := New(followNonLocalRedirects)
    65  
    66  	// take some time to wait server boot
    67  	time.Sleep(2 * time.Second)
    68  	url, err := url.Parse("http://example.com")
    69  	if err != nil {
    70  		t.Errorf("proxy test unexpected error: %v", err)
    71  	}
    72  
    73  	req, err := NewProbeRequest(url, http.Header{})
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  
    78  	_, response, _ := prober.Probe(req, time.Second*3)
    79  
    80  	if response == res {
    81  		t.Errorf("proxy test unexpected error: the probe is using proxy")
    82  	}
    83  }
    84  
    85  func TestHTTPProbeChecker(t *testing.T) {
    86  	handleReq := func(s int, body string) func(w http.ResponseWriter, r *http.Request) {
    87  		return func(w http.ResponseWriter, r *http.Request) {
    88  			w.WriteHeader(s)
    89  			w.Write([]byte(body))
    90  		}
    91  	}
    92  
    93  	// Echo handler that returns the contents of request headers in the body
    94  	headerEchoHandler := func(w http.ResponseWriter, r *http.Request) {
    95  		w.WriteHeader(200)
    96  		output := ""
    97  		for k, arr := range r.Header {
    98  			for _, v := range arr {
    99  				output += fmt.Sprintf("%s: %s\n", k, v)
   100  			}
   101  		}
   102  		w.Write([]byte(output))
   103  	}
   104  
   105  	// Handler that returns the number of request headers in the body
   106  	headerCounterHandler := func(w http.ResponseWriter, r *http.Request) {
   107  		w.WriteHeader(200)
   108  		w.Write([]byte(strconv.Itoa(len(r.Header))))
   109  	}
   110  
   111  	// Handler that returns the keys of request headers in the body
   112  	headerKeysNamesHandler := func(w http.ResponseWriter, r *http.Request) {
   113  		w.WriteHeader(200)
   114  		keys := make([]string, 0, len(r.Header))
   115  		for k := range r.Header {
   116  			keys = append(keys, k)
   117  		}
   118  		sort.Strings(keys)
   119  
   120  		w.Write([]byte(strings.Join(keys, "\n")))
   121  	}
   122  
   123  	redirectHandler := func(s int, bad bool) func(w http.ResponseWriter, r *http.Request) {
   124  		return func(w http.ResponseWriter, r *http.Request) {
   125  			if r.URL.Path == "/" {
   126  				http.Redirect(w, r, "/new", s)
   127  			} else if bad && r.URL.Path == "/new" {
   128  				http.Error(w, "", http.StatusInternalServerError)
   129  			}
   130  		}
   131  	}
   132  
   133  	redirectHandlerWithBody := func(s int, body string) func(w http.ResponseWriter, r *http.Request) {
   134  		return func(w http.ResponseWriter, r *http.Request) {
   135  			if r.URL.Path == "/" {
   136  				http.Redirect(w, r, "/new", s)
   137  			} else if r.URL.Path == "/new" {
   138  				w.WriteHeader(s)
   139  				w.Write([]byte(body))
   140  			}
   141  		}
   142  	}
   143  
   144  	followNonLocalRedirects := true
   145  	prober := New(followNonLocalRedirects)
   146  	testCases := []struct {
   147  		handler    func(w http.ResponseWriter, r *http.Request)
   148  		reqHeaders http.Header
   149  		health     probe.Result
   150  		accBody    string
   151  		notBody    string
   152  	}{
   153  		// The probe will be filled in below.  This is primarily testing that an HTTP GET happens.
   154  		{
   155  			handler: handleReq(http.StatusOK, "ok body"),
   156  			health:  probe.Success,
   157  			accBody: "ok body",
   158  		},
   159  		{
   160  			handler:    headerCounterHandler,
   161  			reqHeaders: http.Header{},
   162  			health:     probe.Success,
   163  			accBody:    "3",
   164  		},
   165  		{
   166  			handler:    headerKeysNamesHandler,
   167  			reqHeaders: http.Header{},
   168  			health:     probe.Success,
   169  			accBody:    "Accept\nConnection\nUser-Agent",
   170  		},
   171  		{
   172  			handler: headerEchoHandler,
   173  			reqHeaders: http.Header{
   174  				"Accept-Encoding": {"gzip"},
   175  			},
   176  			health:  probe.Success,
   177  			accBody: "Accept-Encoding: gzip",
   178  		},
   179  		{
   180  			handler: headerEchoHandler,
   181  			reqHeaders: http.Header{
   182  				"Accept-Encoding": {"foo"},
   183  			},
   184  			health:  probe.Success,
   185  			accBody: "Accept-Encoding: foo",
   186  		},
   187  		{
   188  			handler: headerEchoHandler,
   189  			reqHeaders: http.Header{
   190  				"Accept-Encoding": {""},
   191  			},
   192  			health:  probe.Success,
   193  			accBody: "Accept-Encoding: \n",
   194  		},
   195  		{
   196  			handler: headerEchoHandler,
   197  			reqHeaders: http.Header{
   198  				"X-Muffins-Or-Cupcakes": {"muffins"},
   199  			},
   200  			health:  probe.Success,
   201  			accBody: "X-Muffins-Or-Cupcakes: muffins",
   202  		},
   203  		{
   204  			handler: headerEchoHandler,
   205  			reqHeaders: http.Header{
   206  				"User-Agent": {"foo/1.0"},
   207  			},
   208  			health:  probe.Success,
   209  			accBody: "User-Agent: foo/1.0",
   210  		},
   211  		{
   212  			handler: headerEchoHandler,
   213  			reqHeaders: http.Header{
   214  				"User-Agent": {""},
   215  			},
   216  			health:  probe.Success,
   217  			notBody: "User-Agent",
   218  		},
   219  		{
   220  			handler:    headerEchoHandler,
   221  			reqHeaders: http.Header{},
   222  			health:     probe.Success,
   223  			accBody:    "User-Agent: kube-probe/",
   224  		},
   225  		{
   226  			handler: headerEchoHandler,
   227  			reqHeaders: http.Header{
   228  				"User-Agent": {"foo/1.0"},
   229  				"Accept":     {"text/html"},
   230  			},
   231  			health:  probe.Success,
   232  			accBody: "Accept: text/html",
   233  		},
   234  		{
   235  			handler: headerEchoHandler,
   236  			reqHeaders: http.Header{
   237  				"User-Agent": {"foo/1.0"},
   238  				"Accept":     {"foo/*"},
   239  			},
   240  			health:  probe.Success,
   241  			accBody: "User-Agent: foo/1.0",
   242  		},
   243  		{
   244  			handler: headerEchoHandler,
   245  			reqHeaders: http.Header{
   246  				"X-Muffins-Or-Cupcakes": {"muffins"},
   247  				"Accept":                {"foo/*"},
   248  			},
   249  			health:  probe.Success,
   250  			accBody: "X-Muffins-Or-Cupcakes: muffins",
   251  		},
   252  		{
   253  			handler: headerEchoHandler,
   254  			reqHeaders: http.Header{
   255  				"Accept": {"foo/*"},
   256  			},
   257  			health:  probe.Success,
   258  			accBody: "Accept: foo/*",
   259  		},
   260  		{
   261  			handler: headerEchoHandler,
   262  			reqHeaders: http.Header{
   263  				"Accept": {""},
   264  			},
   265  			health:  probe.Success,
   266  			notBody: "Accept:",
   267  		},
   268  		{
   269  			handler: headerEchoHandler,
   270  			reqHeaders: http.Header{
   271  				"User-Agent": {"foo/1.0"},
   272  				"Accept":     {""},
   273  			},
   274  			health:  probe.Success,
   275  			notBody: "Accept:",
   276  		},
   277  		{
   278  			handler:    headerEchoHandler,
   279  			reqHeaders: http.Header{},
   280  			health:     probe.Success,
   281  			accBody:    "Accept: */*",
   282  		},
   283  		{
   284  			// Echo handler that returns the contents of Host in the body
   285  			handler: func(w http.ResponseWriter, r *http.Request) {
   286  				w.WriteHeader(200)
   287  				w.Write([]byte(r.Host))
   288  			},
   289  			reqHeaders: http.Header{
   290  				"Host": {"muffins.cupcakes.org"},
   291  			},
   292  			health:  probe.Success,
   293  			accBody: "muffins.cupcakes.org",
   294  		},
   295  		{
   296  			handler: handleReq(FailureCode, "fail body"),
   297  			health:  probe.Failure,
   298  		},
   299  		{
   300  			handler: handleReq(http.StatusInternalServerError, "fail body"),
   301  			health:  probe.Failure,
   302  		},
   303  		{
   304  			handler: func(w http.ResponseWriter, r *http.Request) {
   305  				time.Sleep(3 * time.Second)
   306  			},
   307  			health: probe.Failure,
   308  		},
   309  		{
   310  			handler: redirectHandler(http.StatusMovedPermanently, false), // 301
   311  			health:  probe.Success,
   312  		},
   313  		{
   314  			handler: redirectHandler(http.StatusMovedPermanently, true), // 301
   315  			health:  probe.Failure,
   316  		},
   317  		{
   318  			handler: redirectHandler(http.StatusFound, false), // 302
   319  			health:  probe.Success,
   320  		},
   321  		{
   322  			handler: redirectHandler(http.StatusFound, true), // 302
   323  			health:  probe.Failure,
   324  		},
   325  		{
   326  			handler: redirectHandler(http.StatusTemporaryRedirect, false), // 307
   327  			health:  probe.Success,
   328  		},
   329  		{
   330  			handler: redirectHandler(http.StatusTemporaryRedirect, true), // 307
   331  			health:  probe.Failure,
   332  		},
   333  		{
   334  			handler: redirectHandler(http.StatusPermanentRedirect, false), // 308
   335  			health:  probe.Success,
   336  		},
   337  		{
   338  			handler: redirectHandler(http.StatusPermanentRedirect, true), // 308
   339  			health:  probe.Failure,
   340  		},
   341  		{
   342  			handler: redirectHandlerWithBody(http.StatusPermanentRedirect, ""), // redirect with empty body
   343  			health:  probe.Warning,
   344  			accBody: "Probe terminated redirects, Response body:",
   345  		},
   346  		{
   347  			handler: redirectHandlerWithBody(http.StatusPermanentRedirect, "ok body"), // redirect with body
   348  			health:  probe.Warning,
   349  			accBody: "Probe terminated redirects, Response body: ok body",
   350  		},
   351  	}
   352  	for i, test := range testCases {
   353  		t.Run(fmt.Sprintf("case-%2d", i), func(t *testing.T) {
   354  			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   355  				test.handler(w, r)
   356  			}))
   357  			defer server.Close()
   358  			u, err := url.Parse(server.URL)
   359  			if err != nil {
   360  				t.Errorf("case %d: unexpected error: %v", i, err)
   361  			}
   362  			_, port, err := net.SplitHostPort(u.Host)
   363  			if err != nil {
   364  				t.Errorf("case %d: unexpected error: %v", i, err)
   365  			}
   366  			_, err = strconv.Atoi(port)
   367  			if err != nil {
   368  				t.Errorf("case %d: unexpected error: %v", i, err)
   369  			}
   370  			req, err := NewProbeRequest(u, test.reqHeaders)
   371  			if err != nil {
   372  				t.Fatal(err)
   373  			}
   374  			health, output, err := prober.Probe(req, 1*time.Second)
   375  			if test.health == probe.Unknown && err == nil {
   376  				t.Errorf("case %d: expected error", i)
   377  			}
   378  			if test.health != probe.Unknown && err != nil {
   379  				t.Errorf("case %d: unexpected error: %v", i, err)
   380  			}
   381  			if health != test.health {
   382  				t.Errorf("case %d: expected %v, got %v", i, test.health, health)
   383  			}
   384  			if health != probe.Failure && test.health != probe.Failure {
   385  				if !strings.Contains(output, test.accBody) {
   386  					t.Errorf("Expected response body to contain %v, got %v", test.accBody, output)
   387  				}
   388  				if test.notBody != "" && strings.Contains(output, test.notBody) {
   389  					t.Errorf("Expected response not to contain %v, got %v", test.notBody, output)
   390  				}
   391  			}
   392  		})
   393  	}
   394  }
   395  
   396  func TestHTTPProbeChecker_NonLocalRedirects(t *testing.T) {
   397  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   398  		switch r.URL.Path {
   399  		case "/redirect":
   400  			loc, _ := url.QueryUnescape(r.URL.Query().Get("loc"))
   401  			http.Redirect(w, r, loc, http.StatusFound)
   402  		case "/loop":
   403  			http.Redirect(w, r, "/loop", http.StatusFound)
   404  		case "/success":
   405  			w.WriteHeader(http.StatusOK)
   406  		default:
   407  			http.Error(w, "", http.StatusInternalServerError)
   408  		}
   409  	})
   410  	server := httptest.NewServer(handler)
   411  	defer server.Close()
   412  
   413  	newportServer := httptest.NewServer(handler)
   414  	defer newportServer.Close()
   415  
   416  	testCases := map[string]struct {
   417  		redirect             string
   418  		expectLocalResult    probe.Result
   419  		expectNonLocalResult probe.Result
   420  	}{
   421  		"local success":   {"/success", probe.Success, probe.Success},
   422  		"local fail":      {"/fail", probe.Failure, probe.Failure},
   423  		"newport success": {newportServer.URL + "/success", probe.Success, probe.Success},
   424  		"newport fail":    {newportServer.URL + "/fail", probe.Failure, probe.Failure},
   425  		"bogus nonlocal":  {"http://0.0.0.0/fail", probe.Warning, probe.Failure},
   426  		"redirect loop":   {"/loop", probe.Failure, probe.Failure},
   427  	}
   428  	for desc, test := range testCases {
   429  		t.Run(desc+"-local", func(t *testing.T) {
   430  			followNonLocalRedirects := false
   431  			prober := New(followNonLocalRedirects)
   432  			target, err := url.Parse(server.URL + "/redirect?loc=" + url.QueryEscape(test.redirect))
   433  			require.NoError(t, err)
   434  			req, err := NewProbeRequest(target, nil)
   435  			require.NoError(t, err)
   436  			result, _, _ := prober.Probe(req, wait.ForeverTestTimeout)
   437  			assert.Equal(t, test.expectLocalResult, result)
   438  		})
   439  		t.Run(desc+"-nonlocal", func(t *testing.T) {
   440  			followNonLocalRedirects := true
   441  			prober := New(followNonLocalRedirects)
   442  			target, err := url.Parse(server.URL + "/redirect?loc=" + url.QueryEscape(test.redirect))
   443  			require.NoError(t, err)
   444  			req, err := NewProbeRequest(target, nil)
   445  			require.NoError(t, err)
   446  			result, _, _ := prober.Probe(req, wait.ForeverTestTimeout)
   447  			assert.Equal(t, test.expectNonLocalResult, result)
   448  		})
   449  	}
   450  }
   451  
   452  func TestHTTPProbeChecker_HostHeaderPreservedAfterRedirect(t *testing.T) {
   453  	successHostHeader := "www.success.com"
   454  	failHostHeader := "www.fail.com"
   455  
   456  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   457  		switch r.URL.Path {
   458  		case "/redirect":
   459  			http.Redirect(w, r, "/success", http.StatusFound)
   460  		case "/success":
   461  			if r.Host == successHostHeader {
   462  				w.WriteHeader(http.StatusOK)
   463  			} else {
   464  				http.Error(w, "", http.StatusBadRequest)
   465  			}
   466  		default:
   467  			http.Error(w, "", http.StatusInternalServerError)
   468  		}
   469  	})
   470  	server := httptest.NewServer(handler)
   471  	defer server.Close()
   472  
   473  	testCases := map[string]struct {
   474  		hostHeader     string
   475  		expectedResult probe.Result
   476  	}{
   477  		"success": {successHostHeader, probe.Success},
   478  		"fail":    {failHostHeader, probe.Failure},
   479  	}
   480  	for desc, test := range testCases {
   481  		headers := http.Header{}
   482  		headers.Add("Host", test.hostHeader)
   483  		t.Run(desc+"local", func(t *testing.T) {
   484  			followNonLocalRedirects := false
   485  			prober := New(followNonLocalRedirects)
   486  			target, err := url.Parse(server.URL + "/redirect")
   487  			require.NoError(t, err)
   488  			req, err := NewProbeRequest(target, headers)
   489  			require.NoError(t, err)
   490  			result, _, _ := prober.Probe(req, wait.ForeverTestTimeout)
   491  			assert.Equal(t, test.expectedResult, result)
   492  		})
   493  		t.Run(desc+"nonlocal", func(t *testing.T) {
   494  			followNonLocalRedirects := true
   495  			prober := New(followNonLocalRedirects)
   496  			target, err := url.Parse(server.URL + "/redirect")
   497  			require.NoError(t, err)
   498  			req, err := NewProbeRequest(target, headers)
   499  			require.NoError(t, err)
   500  			result, _, _ := prober.Probe(req, wait.ForeverTestTimeout)
   501  			assert.Equal(t, test.expectedResult, result)
   502  		})
   503  	}
   504  }
   505  
   506  func TestHTTPProbeChecker_PayloadTruncated(t *testing.T) {
   507  	successHostHeader := "www.success.com"
   508  	oversizePayload := bytes.Repeat([]byte("a"), maxRespBodyLength+1)
   509  	truncatedPayload := bytes.Repeat([]byte("a"), maxRespBodyLength)
   510  
   511  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   512  		switch r.URL.Path {
   513  		case "/success":
   514  			if r.Host == successHostHeader {
   515  				w.WriteHeader(http.StatusOK)
   516  				w.Write(oversizePayload)
   517  			} else {
   518  				http.Error(w, "", http.StatusBadRequest)
   519  			}
   520  		default:
   521  			http.Error(w, "", http.StatusInternalServerError)
   522  		}
   523  	})
   524  	server := httptest.NewServer(handler)
   525  	defer server.Close()
   526  
   527  	headers := http.Header{}
   528  	headers.Add("Host", successHostHeader)
   529  	t.Run("truncated payload", func(t *testing.T) {
   530  		prober := New(false)
   531  		target, err := url.Parse(server.URL + "/success")
   532  		require.NoError(t, err)
   533  		req, err := NewProbeRequest(target, headers)
   534  		require.NoError(t, err)
   535  		result, body, err := prober.Probe(req, wait.ForeverTestTimeout)
   536  		assert.NoError(t, err)
   537  		assert.Equal(t, probe.Success, result)
   538  		assert.Equal(t, string(truncatedPayload), body)
   539  	})
   540  }
   541  
   542  func TestHTTPProbeChecker_PayloadNormal(t *testing.T) {
   543  	successHostHeader := "www.success.com"
   544  	normalPayload := bytes.Repeat([]byte("a"), maxRespBodyLength-1)
   545  
   546  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   547  		switch r.URL.Path {
   548  		case "/success":
   549  			if r.Host == successHostHeader {
   550  				w.WriteHeader(http.StatusOK)
   551  				w.Write(normalPayload)
   552  			} else {
   553  				http.Error(w, "", http.StatusBadRequest)
   554  			}
   555  		default:
   556  			http.Error(w, "", http.StatusInternalServerError)
   557  		}
   558  	})
   559  	server := httptest.NewServer(handler)
   560  	defer server.Close()
   561  
   562  	headers := http.Header{}
   563  	headers.Add("Host", successHostHeader)
   564  	t.Run("normal payload", func(t *testing.T) {
   565  		prober := New(false)
   566  		target, err := url.Parse(server.URL + "/success")
   567  		require.NoError(t, err)
   568  		req, err := NewProbeRequest(target, headers)
   569  		require.NoError(t, err)
   570  		result, body, err := prober.Probe(req, wait.ForeverTestTimeout)
   571  		assert.NoError(t, err)
   572  		assert.Equal(t, probe.Success, result)
   573  		assert.Equal(t, string(normalPayload), body)
   574  	})
   575  }
   576  

View as plain text