k8s.io/client-go@v0.31.1/rest/client_test.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 rest
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"net/http/httputil"
    25  	"net/url"
    26  	"reflect"
    27  	"testing"
    28  	"time"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	v1beta1 "k8s.io/api/extensions/v1beta1"
    32  	"k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/client-go/kubernetes/scheme"
    37  	utiltesting "k8s.io/client-go/util/testing"
    38  
    39  	"github.com/google/go-cmp/cmp"
    40  )
    41  
    42  type TestParam struct {
    43  	actualError           error
    44  	expectingError        bool
    45  	actualCreated         bool
    46  	expCreated            bool
    47  	expStatus             *metav1.Status
    48  	testBody              bool
    49  	testBodyErrorIsNotNil bool
    50  }
    51  
    52  // TestSerializer makes sure that you're always able to decode metav1.Status
    53  func TestSerializer(t *testing.T) {
    54  	gv := v1beta1.SchemeGroupVersion
    55  	contentConfig := ContentConfig{
    56  		ContentType:          "application/json",
    57  		GroupVersion:         &gv,
    58  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
    59  	}
    60  
    61  	n := runtime.NewClientNegotiator(contentConfig.NegotiatedSerializer, gv)
    62  	d, err := n.Decoder("application/json", nil)
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	// bytes based on actual return from API server when encoding an "unversioned" object
    68  	obj, err := runtime.Decode(d, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`))
    69  	t.Log(obj)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  }
    74  
    75  func TestDoRequestSuccess(t *testing.T) {
    76  	testServer, fakeHandler, status := testServerEnv(t, 200)
    77  	defer testServer.Close()
    78  
    79  	c, err := restClient(testServer)
    80  	if err != nil {
    81  		t.Fatalf("unexpected error: %v", err)
    82  	}
    83  	body, err := c.Get().Prefix("test").Do(context.Background()).Raw()
    84  
    85  	testParam := TestParam{actualError: err, expectingError: false, expCreated: true,
    86  		expStatus: status, testBody: true, testBodyErrorIsNotNil: false}
    87  	validate(testParam, t, body, fakeHandler)
    88  }
    89  
    90  func TestDoRequestFailed(t *testing.T) {
    91  	status := &metav1.Status{
    92  		Code:    http.StatusNotFound,
    93  		Status:  metav1.StatusFailure,
    94  		Reason:  metav1.StatusReasonNotFound,
    95  		Message: " \"\" not found",
    96  		Details: &metav1.StatusDetails{},
    97  	}
    98  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
    99  	fakeHandler := utiltesting.FakeHandler{
   100  		StatusCode:   404,
   101  		ResponseBody: string(expectedBody),
   102  		T:            t,
   103  	}
   104  	testServer := httptest.NewServer(&fakeHandler)
   105  	defer testServer.Close()
   106  
   107  	c, err := restClient(testServer)
   108  	if err != nil {
   109  		t.Fatalf("unexpected error: %v", err)
   110  	}
   111  	err = c.Get().Do(context.Background()).Error()
   112  	if err == nil {
   113  		t.Errorf("unexpected non-error")
   114  	}
   115  	ss, ok := err.(errors.APIStatus)
   116  	if !ok {
   117  		t.Errorf("unexpected error type %v", err)
   118  	}
   119  	actual := ss.Status()
   120  	if !reflect.DeepEqual(status, &actual) {
   121  		t.Errorf("Unexpected mis-match: %s", cmp.Diff(status, &actual))
   122  	}
   123  }
   124  
   125  func TestDoRawRequestFailed(t *testing.T) {
   126  	status := &metav1.Status{
   127  		Code:    http.StatusNotFound,
   128  		Status:  metav1.StatusFailure,
   129  		Reason:  metav1.StatusReasonNotFound,
   130  		Message: "the server could not find the requested resource",
   131  		Details: &metav1.StatusDetails{
   132  			Causes: []metav1.StatusCause{
   133  				{Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"},
   134  			},
   135  		},
   136  	}
   137  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
   138  	fakeHandler := utiltesting.FakeHandler{
   139  		StatusCode:   404,
   140  		ResponseBody: string(expectedBody),
   141  		T:            t,
   142  	}
   143  	testServer := httptest.NewServer(&fakeHandler)
   144  	defer testServer.Close()
   145  
   146  	c, err := restClient(testServer)
   147  	if err != nil {
   148  		t.Fatalf("unexpected error: %v", err)
   149  	}
   150  	body, err := c.Get().Do(context.Background()).Raw()
   151  
   152  	if err == nil || body == nil {
   153  		t.Errorf("unexpected non-error: %#v", body)
   154  	}
   155  	ss, ok := err.(errors.APIStatus)
   156  	if !ok {
   157  		t.Errorf("unexpected error type %v", err)
   158  	}
   159  	actual := ss.Status()
   160  	if !reflect.DeepEqual(status, &actual) {
   161  		t.Errorf("Unexpected mis-match: %s", cmp.Diff(status, &actual))
   162  	}
   163  }
   164  
   165  func TestDoRequestCreated(t *testing.T) {
   166  	testServer, fakeHandler, status := testServerEnv(t, 201)
   167  	defer testServer.Close()
   168  
   169  	c, err := restClient(testServer)
   170  	if err != nil {
   171  		t.Fatalf("unexpected error: %v", err)
   172  	}
   173  	created := false
   174  	body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw()
   175  
   176  	testParam := TestParam{actualError: err, expectingError: false, expCreated: true,
   177  		expStatus: status, testBody: false}
   178  	validate(testParam, t, body, fakeHandler)
   179  }
   180  
   181  func TestDoRequestNotCreated(t *testing.T) {
   182  	testServer, fakeHandler, expectedStatus := testServerEnv(t, 202)
   183  	defer testServer.Close()
   184  	c, err := restClient(testServer)
   185  	if err != nil {
   186  		t.Fatalf("unexpected error: %v", err)
   187  	}
   188  	created := false
   189  	body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw()
   190  	testParam := TestParam{actualError: err, expectingError: false, expCreated: false,
   191  		expStatus: expectedStatus, testBody: false}
   192  	validate(testParam, t, body, fakeHandler)
   193  }
   194  
   195  func TestDoRequestAcceptedNoContentReturned(t *testing.T) {
   196  	testServer, fakeHandler, _ := testServerEnv(t, 204)
   197  	defer testServer.Close()
   198  
   199  	c, err := restClient(testServer)
   200  	if err != nil {
   201  		t.Fatalf("unexpected error: %v", err)
   202  	}
   203  	created := false
   204  	body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw()
   205  	testParam := TestParam{actualError: err, expectingError: false, expCreated: false,
   206  		testBody: false}
   207  	validate(testParam, t, body, fakeHandler)
   208  }
   209  
   210  func TestBadRequest(t *testing.T) {
   211  	testServer, fakeHandler, _ := testServerEnv(t, 400)
   212  	defer testServer.Close()
   213  	c, err := restClient(testServer)
   214  	if err != nil {
   215  		t.Fatalf("unexpected error: %v", err)
   216  	}
   217  	created := false
   218  	body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw()
   219  	testParam := TestParam{actualError: err, expectingError: true, expCreated: false,
   220  		testBody: true}
   221  	validate(testParam, t, body, fakeHandler)
   222  }
   223  
   224  func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utiltesting.FakeHandler) {
   225  	switch {
   226  	case testParam.expectingError && testParam.actualError == nil:
   227  		t.Errorf("Expected error")
   228  	case !testParam.expectingError && testParam.actualError != nil:
   229  		t.Error(testParam.actualError)
   230  	}
   231  	if !testParam.expCreated {
   232  		if testParam.actualCreated {
   233  			t.Errorf("Expected object not to be created")
   234  		}
   235  	}
   236  	statusOut, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), body)
   237  	if testParam.testBody {
   238  		if testParam.testBodyErrorIsNotNil && err == nil {
   239  			t.Errorf("Expected Error")
   240  		}
   241  		if !testParam.testBodyErrorIsNotNil && err != nil {
   242  			t.Errorf("Unexpected Error: %v", err)
   243  		}
   244  	}
   245  
   246  	if testParam.expStatus != nil {
   247  		if !reflect.DeepEqual(testParam.expStatus, statusOut) {
   248  			t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", testParam.expStatus, statusOut)
   249  		}
   250  	}
   251  	fakeHandler.ValidateRequest(t, "/"+v1.SchemeGroupVersion.String()+"/test", "GET", nil)
   252  
   253  }
   254  
   255  func TestHTTPMethods(t *testing.T) {
   256  	testServer, _, _ := testServerEnv(t, 200)
   257  	defer testServer.Close()
   258  	c, _ := restClient(testServer)
   259  
   260  	request := c.Post()
   261  	if request == nil {
   262  		t.Errorf("Post : Object returned should not be nil")
   263  	}
   264  
   265  	request = c.Get()
   266  	if request == nil {
   267  		t.Errorf("Get: Object returned should not be nil")
   268  	}
   269  
   270  	request = c.Put()
   271  	if request == nil {
   272  		t.Errorf("Put : Object returned should not be nil")
   273  	}
   274  
   275  	request = c.Delete()
   276  	if request == nil {
   277  		t.Errorf("Delete : Object returned should not be nil")
   278  	}
   279  
   280  	request = c.Patch(types.JSONPatchType)
   281  	if request == nil {
   282  		t.Errorf("Patch : Object returned should not be nil")
   283  	}
   284  }
   285  
   286  func TestHTTPProxy(t *testing.T) {
   287  	ctx := context.Background()
   288  	testServer, fh, _ := testServerEnv(t, 200)
   289  	fh.ResponseBody = "backend data"
   290  	defer testServer.Close()
   291  
   292  	testProxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   293  		to, err := url.Parse(req.RequestURI)
   294  		if err != nil {
   295  			t.Fatalf("err: %v", err)
   296  		}
   297  		w.Write([]byte("proxied: "))
   298  		httputil.NewSingleHostReverseProxy(to).ServeHTTP(w, req)
   299  	}))
   300  	defer testProxyServer.Close()
   301  
   302  	t.Logf(testProxyServer.URL)
   303  
   304  	u, err := url.Parse(testProxyServer.URL)
   305  	if err != nil {
   306  		t.Fatalf("Failed to parse test proxy server url: %v", err)
   307  	}
   308  
   309  	c, err := RESTClientFor(&Config{
   310  		Host: testServer.URL,
   311  		ContentConfig: ContentConfig{
   312  			GroupVersion:         &v1.SchemeGroupVersion,
   313  			NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   314  		},
   315  		Proxy:    http.ProxyURL(u),
   316  		Username: "user",
   317  		Password: "pass",
   318  	})
   319  	if err != nil {
   320  		t.Fatalf("Failed to create client: %v", err)
   321  	}
   322  
   323  	request := c.Get()
   324  	if request == nil {
   325  		t.Fatalf("Get: Object returned should not be nil")
   326  	}
   327  
   328  	b, err := request.DoRaw(ctx)
   329  	if err != nil {
   330  		t.Fatalf("unexpected err: %v", err)
   331  	}
   332  	if got, want := string(b), "proxied: backend data"; !cmp.Equal(got, want) {
   333  		t.Errorf("unexpected body: %v", cmp.Diff(want, got))
   334  	}
   335  }
   336  
   337  func TestCreateBackoffManager(t *testing.T) {
   338  
   339  	theUrl, _ := url.Parse("http://localhost")
   340  
   341  	// 1 second base backoff + duration of 2 seconds -> exponential backoff for requests.
   342  	t.Setenv(envBackoffBase, "1")
   343  	t.Setenv(envBackoffDuration, "2")
   344  	backoff := readExpBackoffConfig()
   345  	backoff.UpdateBackoff(theUrl, nil, 500)
   346  	backoff.UpdateBackoff(theUrl, nil, 500)
   347  	if backoff.CalculateBackoff(theUrl)/time.Second != 2 {
   348  		t.Errorf("Backoff env not working.")
   349  	}
   350  
   351  	// 0 duration -> no backoff.
   352  	t.Setenv(envBackoffBase, "1")
   353  	t.Setenv(envBackoffDuration, "0")
   354  	backoff.UpdateBackoff(theUrl, nil, 500)
   355  	backoff.UpdateBackoff(theUrl, nil, 500)
   356  	backoff = readExpBackoffConfig()
   357  	if backoff.CalculateBackoff(theUrl)/time.Second != 0 {
   358  		t.Errorf("Zero backoff duration, but backoff still occurring.")
   359  	}
   360  
   361  	// No env -> No backoff.
   362  	t.Setenv(envBackoffBase, "")
   363  	t.Setenv(envBackoffDuration, "")
   364  	backoff = readExpBackoffConfig()
   365  	backoff.UpdateBackoff(theUrl, nil, 500)
   366  	backoff.UpdateBackoff(theUrl, nil, 500)
   367  	if backoff.CalculateBackoff(theUrl)/time.Second != 0 {
   368  		t.Errorf("Backoff should have been 0.")
   369  	}
   370  
   371  }
   372  
   373  func testServerEnv(t *testing.T, statusCode int) (*httptest.Server, *utiltesting.FakeHandler, *metav1.Status) {
   374  	status := &metav1.Status{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Status"}, Status: fmt.Sprintf("%s", metav1.StatusSuccess)}
   375  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
   376  	fakeHandler := utiltesting.FakeHandler{
   377  		StatusCode:   statusCode,
   378  		ResponseBody: string(expectedBody),
   379  		T:            t,
   380  	}
   381  	testServer := httptest.NewServer(&fakeHandler)
   382  	return testServer, &fakeHandler, status
   383  }
   384  
   385  func restClient(testServer *httptest.Server) (*RESTClient, error) {
   386  	c, err := RESTClientFor(&Config{
   387  		Host: testServer.URL,
   388  		ContentConfig: ContentConfig{
   389  			GroupVersion:         &v1.SchemeGroupVersion,
   390  			NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   391  		},
   392  		Username: "user",
   393  		Password: "pass",
   394  	})
   395  	return c, err
   396  }