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