k8s.io/client-go@v0.22.2/rest/request_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  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"net"
    28  	"net/http"
    29  	"net/http/httptest"
    30  	"net/url"
    31  	"os"
    32  	"reflect"
    33  	"strings"
    34  	"sync"
    35  	"syscall"
    36  	"testing"
    37  	"time"
    38  
    39  	"k8s.io/klog/v2"
    40  
    41  	v1 "k8s.io/api/core/v1"
    42  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    43  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  	"k8s.io/apimachinery/pkg/runtime"
    46  	"k8s.io/apimachinery/pkg/runtime/schema"
    47  	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
    48  	"k8s.io/apimachinery/pkg/util/clock"
    49  	"k8s.io/apimachinery/pkg/util/diff"
    50  	"k8s.io/apimachinery/pkg/util/httpstream"
    51  	"k8s.io/apimachinery/pkg/util/intstr"
    52  	"k8s.io/apimachinery/pkg/watch"
    53  	"k8s.io/client-go/kubernetes/scheme"
    54  	restclientwatch "k8s.io/client-go/rest/watch"
    55  	"k8s.io/client-go/util/flowcontrol"
    56  	utiltesting "k8s.io/client-go/util/testing"
    57  )
    58  
    59  func TestNewRequestSetsAccept(t *testing.T) {
    60  	r := NewRequestWithClient(&url.URL{Path: "/path/"}, "", ClientContentConfig{}, nil).Verb("get")
    61  	if r.headers.Get("Accept") != "" {
    62  		t.Errorf("unexpected headers: %#v", r.headers)
    63  	}
    64  	r = NewRequestWithClient(&url.URL{Path: "/path/"}, "", ClientContentConfig{ContentType: "application/other"}, nil).Verb("get")
    65  	if r.headers.Get("Accept") != "application/other, */*" {
    66  		t.Errorf("unexpected headers: %#v", r.headers)
    67  	}
    68  }
    69  
    70  func clientForFunc(fn clientFunc) *http.Client {
    71  	return &http.Client{
    72  		Transport: fn,
    73  	}
    74  }
    75  
    76  type clientFunc func(req *http.Request) (*http.Response, error)
    77  
    78  func (f clientFunc) RoundTrip(req *http.Request) (*http.Response, error) {
    79  	return f(req)
    80  }
    81  
    82  func TestRequestSetsHeaders(t *testing.T) {
    83  	server := clientForFunc(func(req *http.Request) (*http.Response, error) {
    84  		if req.Header.Get("Accept") != "application/other, */*" {
    85  			t.Errorf("unexpected headers: %#v", req.Header)
    86  		}
    87  		return &http.Response{
    88  			StatusCode: http.StatusForbidden,
    89  			Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
    90  		}, nil
    91  	})
    92  	config := defaultContentConfig()
    93  	config.ContentType = "application/other"
    94  	r := NewRequestWithClient(&url.URL{Path: "/path"}, "", config, nil).Verb("get")
    95  	r.c.Client = server
    96  
    97  	// Check if all "issue" methods are setting headers.
    98  	_ = r.Do(context.Background())
    99  	_, _ = r.Watch(context.Background())
   100  	_, _ = r.Stream(context.Background())
   101  }
   102  
   103  func TestRequestWithErrorWontChange(t *testing.T) {
   104  	gvCopy := v1.SchemeGroupVersion
   105  	original := Request{
   106  		err: errors.New("test"),
   107  		c: &RESTClient{
   108  			content: ClientContentConfig{GroupVersion: gvCopy},
   109  		},
   110  	}
   111  	r := original
   112  	changed := r.Param("foo", "bar").
   113  		AbsPath("/abs").
   114  		Prefix("test").
   115  		Suffix("testing").
   116  		Namespace("new").
   117  		Resource("foos").
   118  		Name("bars").
   119  		Body("foo").
   120  		Timeout(time.Millisecond)
   121  	if changed != &r {
   122  		t.Errorf("returned request should point to the same object")
   123  	}
   124  	if !reflect.DeepEqual(changed, &original) {
   125  		t.Errorf("expected %#v, got %#v", &original, changed)
   126  	}
   127  }
   128  
   129  func TestRequestPreservesBaseTrailingSlash(t *testing.T) {
   130  	r := &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "/path/"}
   131  	if s := r.URL().String(); s != "/path/" {
   132  		t.Errorf("trailing slash should be preserved: %s", s)
   133  	}
   134  }
   135  
   136  func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
   137  	r := (&Request{c: &RESTClient{base: &url.URL{}}}).AbsPath("/foo/")
   138  	if s := r.URL().String(); s != "/foo/" {
   139  		t.Errorf("trailing slash should be preserved: %s", s)
   140  	}
   141  
   142  	r = (&Request{c: &RESTClient{base: &url.URL{}}}).AbsPath("/foo/")
   143  	if s := r.URL().String(); s != "/foo/" {
   144  		t.Errorf("trailing slash should be preserved: %s", s)
   145  	}
   146  }
   147  
   148  func TestRequestAbsPathJoins(t *testing.T) {
   149  	r := (&Request{c: &RESTClient{base: &url.URL{}}}).AbsPath("foo/bar", "baz")
   150  	if s := r.URL().String(); s != "foo/bar/baz" {
   151  		t.Errorf("trailing slash should be preserved: %s", s)
   152  	}
   153  }
   154  
   155  func TestRequestSetsNamespace(t *testing.T) {
   156  	r := (&Request{
   157  		c: &RESTClient{base: &url.URL{Path: "/"}},
   158  	}).Namespace("foo")
   159  	if r.namespace == "" {
   160  		t.Errorf("namespace should be set: %#v", r)
   161  	}
   162  
   163  	if s := r.URL().String(); s != "namespaces/foo" {
   164  		t.Errorf("namespace should be in path: %s", s)
   165  	}
   166  }
   167  
   168  func TestRequestOrdersNamespaceInPath(t *testing.T) {
   169  	r := (&Request{
   170  		c:          &RESTClient{base: &url.URL{}},
   171  		pathPrefix: "/test/",
   172  	}).Name("bar").Resource("baz").Namespace("foo")
   173  	if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar" {
   174  		t.Errorf("namespace should be in order in path: %s", s)
   175  	}
   176  }
   177  
   178  func TestRequestOrdersSubResource(t *testing.T) {
   179  	r := (&Request{
   180  		c:          &RESTClient{base: &url.URL{}},
   181  		pathPrefix: "/test/",
   182  	}).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b")
   183  	if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar/a/b/test" {
   184  		t.Errorf("namespace should be in order in path: %s", s)
   185  	}
   186  }
   187  
   188  func TestRequestSetTwiceError(t *testing.T) {
   189  	if (&Request{}).Name("bar").Name("baz").err == nil {
   190  		t.Errorf("setting name twice should result in error")
   191  	}
   192  	if (&Request{}).Namespace("bar").Namespace("baz").err == nil {
   193  		t.Errorf("setting namespace twice should result in error")
   194  	}
   195  	if (&Request{}).Resource("bar").Resource("baz").err == nil {
   196  		t.Errorf("setting resource twice should result in error")
   197  	}
   198  	if (&Request{}).SubResource("bar").SubResource("baz").err == nil {
   199  		t.Errorf("setting subresource twice should result in error")
   200  	}
   201  }
   202  
   203  func TestInvalidSegments(t *testing.T) {
   204  	invalidSegments := []string{".", "..", "test/segment", "test%2bsegment"}
   205  	setters := map[string]func(string, *Request){
   206  		"namespace":   func(s string, r *Request) { r.Namespace(s) },
   207  		"resource":    func(s string, r *Request) { r.Resource(s) },
   208  		"name":        func(s string, r *Request) { r.Name(s) },
   209  		"subresource": func(s string, r *Request) { r.SubResource(s) },
   210  	}
   211  	for _, invalidSegment := range invalidSegments {
   212  		for setterName, setter := range setters {
   213  			r := &Request{}
   214  			setter(invalidSegment, r)
   215  			if r.err == nil {
   216  				t.Errorf("%s: %s: expected error, got none", setterName, invalidSegment)
   217  			}
   218  		}
   219  	}
   220  }
   221  
   222  func TestRequestParam(t *testing.T) {
   223  	r := (&Request{}).Param("foo", "a")
   224  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) {
   225  		t.Errorf("should have set a param: %#v", r)
   226  	}
   227  
   228  	r.Param("bar", "1")
   229  	r.Param("bar", "2")
   230  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}, "bar": []string{"1", "2"}}) {
   231  		t.Errorf("should have set a param: %#v", r)
   232  	}
   233  }
   234  
   235  func TestRequestVersionedParams(t *testing.T) {
   236  	r := (&Request{c: &RESTClient{content: ClientContentConfig{GroupVersion: v1.SchemeGroupVersion}}}).Param("foo", "a")
   237  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) {
   238  		t.Errorf("should have set a param: %#v", r)
   239  	}
   240  	r.VersionedParams(&v1.PodLogOptions{Follow: true, Container: "bar"}, scheme.ParameterCodec)
   241  
   242  	if !reflect.DeepEqual(r.params, url.Values{
   243  		"foo":       []string{"a"},
   244  		"container": []string{"bar"},
   245  		"follow":    []string{"true"},
   246  	}) {
   247  		t.Errorf("should have set a param: %#v", r)
   248  	}
   249  }
   250  
   251  func TestRequestVersionedParamsFromListOptions(t *testing.T) {
   252  	r := &Request{c: &RESTClient{content: ClientContentConfig{GroupVersion: v1.SchemeGroupVersion}}}
   253  	r.VersionedParams(&metav1.ListOptions{ResourceVersion: "1"}, scheme.ParameterCodec)
   254  	if !reflect.DeepEqual(r.params, url.Values{
   255  		"resourceVersion": []string{"1"},
   256  	}) {
   257  		t.Errorf("should have set a param: %#v", r)
   258  	}
   259  
   260  	var timeout int64 = 10
   261  	r.VersionedParams(&metav1.ListOptions{ResourceVersion: "2", TimeoutSeconds: &timeout}, scheme.ParameterCodec)
   262  	if !reflect.DeepEqual(r.params, url.Values{
   263  		"resourceVersion": []string{"1", "2"},
   264  		"timeoutSeconds":  []string{"10"},
   265  	}) {
   266  		t.Errorf("should have set a param: %#v %v", r.params, r.err)
   267  	}
   268  }
   269  
   270  func TestRequestURI(t *testing.T) {
   271  	r := (&Request{}).Param("foo", "a")
   272  	r.Prefix("other")
   273  	r.RequestURI("/test?foo=b&a=b&c=1&c=2")
   274  	if r.pathPrefix != "/test" {
   275  		t.Errorf("path is wrong: %#v", r)
   276  	}
   277  	if !reflect.DeepEqual(r.params, url.Values{"a": []string{"b"}, "foo": []string{"b"}, "c": []string{"1", "2"}}) {
   278  		t.Errorf("should have set a param: %#v", r)
   279  	}
   280  }
   281  
   282  type NotAnAPIObject struct{}
   283  
   284  func (obj NotAnAPIObject) GroupVersionKind() *schema.GroupVersionKind       { return nil }
   285  func (obj NotAnAPIObject) SetGroupVersionKind(gvk *schema.GroupVersionKind) {}
   286  
   287  func defaultContentConfig() ClientContentConfig {
   288  	gvCopy := v1.SchemeGroupVersion
   289  	return ClientContentConfig{
   290  		ContentType:  "application/json",
   291  		GroupVersion: gvCopy,
   292  		Negotiator:   runtime.NewClientNegotiator(scheme.Codecs.WithoutConversion(), gvCopy),
   293  	}
   294  }
   295  
   296  func TestRequestBody(t *testing.T) {
   297  	// test unknown type
   298  	r := (&Request{}).Body([]string{"test"})
   299  	if r.err == nil || r.body != nil {
   300  		t.Errorf("should have set err and left body nil: %#v", r)
   301  	}
   302  
   303  	// test error set when failing to read file
   304  	f, err := ioutil.TempFile("", "test")
   305  	if err != nil {
   306  		t.Fatalf("unable to create temp file")
   307  	}
   308  	defer f.Close()
   309  	os.Remove(f.Name())
   310  	r = (&Request{}).Body(f.Name())
   311  	if r.err == nil || r.body != nil {
   312  		t.Errorf("should have set err and left body nil: %#v", r)
   313  	}
   314  
   315  	// test unencodable api object
   316  	r = (&Request{c: &RESTClient{content: defaultContentConfig()}}).Body(&NotAnAPIObject{})
   317  	if r.err == nil || r.body != nil {
   318  		t.Errorf("should have set err and left body nil: %#v", r)
   319  	}
   320  }
   321  
   322  func TestResultIntoWithErrReturnsErr(t *testing.T) {
   323  	res := Result{err: errors.New("test")}
   324  	if err := res.Into(&v1.Pod{}); err != res.err {
   325  		t.Errorf("should have returned exact error from result")
   326  	}
   327  }
   328  
   329  func TestResultIntoWithNoBodyReturnsErr(t *testing.T) {
   330  	res := Result{
   331  		body:    []byte{},
   332  		decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   333  	}
   334  	if err := res.Into(&v1.Pod{}); err == nil || !strings.Contains(err.Error(), "0-length") {
   335  		t.Errorf("should have complained about 0 length body")
   336  	}
   337  }
   338  
   339  func TestURLTemplate(t *testing.T) {
   340  	uri, _ := url.Parse("http://localhost/some/base/url/path")
   341  	uriSingleSlash, _ := url.Parse("http://localhost/")
   342  	testCases := []struct {
   343  		Request          *Request
   344  		ExpectedFullURL  string
   345  		ExpectedFinalURL string
   346  	}{
   347  		{
   348  			// non dynamic client
   349  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
   350  				Prefix("api", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
   351  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/nm?p0=v0",
   352  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D",
   353  		},
   354  		{
   355  			// non dynamic client with wrong api group
   356  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
   357  				Prefix("pre1", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
   358  			ExpectedFullURL:  "http://localhost/some/base/url/path/pre1/v1/namespaces/ns/r1/nm?p0=v0",
   359  			ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
   360  		},
   361  		{
   362  			// dynamic client with core group + namespace + resourceResource (with name)
   363  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   364  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   365  				Prefix("/api/v1/namespaces/ns/r1/name1"),
   366  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/name1",
   367  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
   368  		},
   369  		{
   370  			// dynamic client with named group + namespace + resourceResource (with name)
   371  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   372  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   373  				Prefix("/apis/g1/v1/namespaces/ns/r1/name1"),
   374  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1/name1",
   375  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
   376  		},
   377  		{
   378  			// dynamic client with core group + namespace + resourceResource (with NO name)
   379  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
   380  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   381  				Prefix("/api/v1/namespaces/ns/r1"),
   382  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1",
   383  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1",
   384  		},
   385  		{
   386  			// dynamic client with named group + namespace + resourceResource (with NO name)
   387  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
   388  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   389  				Prefix("/apis/g1/v1/namespaces/ns/r1"),
   390  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1",
   391  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1",
   392  		},
   393  		{
   394  			// dynamic client with core group + resourceResource (with name)
   395  			// /api/$RESOURCEVERSION/$RESOURCE/%NAME
   396  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   397  				Prefix("/api/v1/r1/name1"),
   398  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/r1/name1",
   399  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/r1/%7Bname%7D",
   400  		},
   401  		{
   402  			// dynamic client with named group + resourceResource (with name)
   403  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   404  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   405  				Prefix("/apis/g1/v1/r1/name1"),
   406  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/r1/name1",
   407  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/%7Bname%7D",
   408  		},
   409  		{
   410  			// dynamic client with named group + namespace + resourceResource (with name) + subresource
   411  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
   412  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   413  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   414  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   415  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D/finalize",
   416  		},
   417  		{
   418  			// dynamic client with named group + namespace + resourceResource (with name)
   419  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   420  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   421  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces"),
   422  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces",
   423  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D",
   424  		},
   425  		{
   426  			// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
   427  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
   428  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   429  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   430  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   431  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/finalize",
   432  		},
   433  		{
   434  			// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
   435  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
   436  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   437  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status"),
   438  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status",
   439  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/status",
   440  		},
   441  		{
   442  			// dynamic client with named group + namespace + resourceResource (with no name)
   443  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   444  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   445  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces"),
   446  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces",
   447  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces",
   448  		},
   449  		{
   450  			// dynamic client with named group + resourceResource (with name) + subresource
   451  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   452  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   453  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/finalize"),
   454  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/finalize",
   455  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/finalize",
   456  		},
   457  		{
   458  			// dynamic client with named group + resourceResource (with name) + subresource
   459  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   460  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   461  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/status"),
   462  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/status",
   463  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/status",
   464  		},
   465  		{
   466  			// dynamic client with named group + resourceResource (with name)
   467  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   468  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   469  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces"),
   470  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces",
   471  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D",
   472  		},
   473  		{
   474  			// dynamic client with named group + resourceResource (with no name)
   475  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   476  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   477  				Prefix("/apis/namespaces/namespaces/namespaces"),
   478  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
   479  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
   480  		},
   481  		{
   482  			// dynamic client with wrong api group + namespace + resourceResource (with name) + subresource
   483  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
   484  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   485  				Prefix("/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   486  			ExpectedFullURL:  "http://localhost/some/base/url/path/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   487  			ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
   488  		},
   489  		{
   490  			// dynamic client with core group + namespace + resourceResource (with name) where baseURL is a single /
   491  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   492  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   493  				Prefix("/api/v1/namespaces/ns/r2/name1"),
   494  			ExpectedFullURL:  "http://localhost/api/v1/namespaces/ns/r2/name1",
   495  			ExpectedFinalURL: "http://localhost/api/v1/namespaces/%7Bnamespace%7D/r2/%7Bname%7D",
   496  		},
   497  		{
   498  			// dynamic client with core group + namespace + resourceResource (with name) where baseURL is 'some/base/url/path'
   499  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   500  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   501  				Prefix("/api/v1/namespaces/ns/r3/name1"),
   502  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r3/name1",
   503  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r3/%7Bname%7D",
   504  		},
   505  		{
   506  			// dynamic client where baseURL is a single /
   507  			// /
   508  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   509  				Prefix("/"),
   510  			ExpectedFullURL:  "http://localhost/",
   511  			ExpectedFinalURL: "http://localhost/",
   512  		},
   513  		{
   514  			// dynamic client where baseURL is a single /
   515  			// /version
   516  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   517  				Prefix("/version"),
   518  			ExpectedFullURL:  "http://localhost/version",
   519  			ExpectedFinalURL: "http://localhost/version",
   520  		},
   521  	}
   522  	for i, testCase := range testCases {
   523  		r := testCase.Request
   524  		full := r.URL()
   525  		if full.String() != testCase.ExpectedFullURL {
   526  			t.Errorf("%d: unexpected initial URL: %s %s", i, full, testCase.ExpectedFullURL)
   527  		}
   528  		actualURL := r.finalURLTemplate()
   529  		actual := actualURL.String()
   530  		if actual != testCase.ExpectedFinalURL {
   531  			t.Errorf("%d: unexpected URL template: %s %s", i, actual, testCase.ExpectedFinalURL)
   532  		}
   533  		if r.URL().String() != full.String() {
   534  			t.Errorf("%d, creating URL template changed request: %s -> %s", i, full.String(), r.URL().String())
   535  		}
   536  	}
   537  }
   538  
   539  func TestTransformResponse(t *testing.T) {
   540  	invalid := []byte("aaaaa")
   541  	uri, _ := url.Parse("http://localhost")
   542  	testCases := []struct {
   543  		Response *http.Response
   544  		Data     []byte
   545  		Created  bool
   546  		Error    bool
   547  		ErrFn    func(err error) bool
   548  	}{
   549  		{Response: &http.Response{StatusCode: http.StatusOK}, Data: []byte{}},
   550  		{Response: &http.Response{StatusCode: http.StatusCreated}, Data: []byte{}, Created: true},
   551  		{Response: &http.Response{StatusCode: 199}, Error: true},
   552  		{Response: &http.Response{StatusCode: http.StatusInternalServerError}, Error: true},
   553  		{Response: &http.Response{StatusCode: http.StatusUnprocessableEntity}, Error: true},
   554  		{Response: &http.Response{StatusCode: http.StatusConflict}, Error: true},
   555  		{Response: &http.Response{StatusCode: http.StatusNotFound}, Error: true},
   556  		{Response: &http.Response{StatusCode: http.StatusUnauthorized}, Error: true},
   557  		{
   558  			Response: &http.Response{
   559  				StatusCode: http.StatusUnauthorized,
   560  				Header:     http.Header{"Content-Type": []string{"application/json"}},
   561  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   562  			},
   563  			Error: true,
   564  			ErrFn: func(err error) bool {
   565  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   566  			},
   567  		},
   568  		{
   569  			Response: &http.Response{
   570  				StatusCode: http.StatusUnauthorized,
   571  				Header:     http.Header{"Content-Type": []string{"text/any"}},
   572  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   573  			},
   574  			Error: true,
   575  			ErrFn: func(err error) bool {
   576  				return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err)
   577  			},
   578  		},
   579  		{Response: &http.Response{StatusCode: http.StatusForbidden}, Error: true},
   580  		{Response: &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
   581  		{Response: &http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
   582  	}
   583  	for i, test := range testCases {
   584  		r := NewRequestWithClient(uri, "", defaultContentConfig(), nil)
   585  		if test.Response.Body == nil {
   586  			test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
   587  		}
   588  		result := r.transformResponse(test.Response, &http.Request{})
   589  		response, created, err := result.body, result.statusCode == http.StatusCreated, result.err
   590  		hasErr := err != nil
   591  		if hasErr != test.Error {
   592  			t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)
   593  		} else if hasErr && test.Response.StatusCode > 399 {
   594  			status, ok := err.(apierrors.APIStatus)
   595  			if !ok {
   596  				t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err)
   597  				continue
   598  			}
   599  			if int(status.Status().Code) != test.Response.StatusCode {
   600  				t.Errorf("%d: status code did not match response: %#v", i, status.Status())
   601  			}
   602  		}
   603  		if test.ErrFn != nil && !test.ErrFn(err) {
   604  			t.Errorf("%d: error function did not match: %v", i, err)
   605  		}
   606  		if !(test.Data == nil && response == nil) && !apiequality.Semantic.DeepDerivative(test.Data, response) {
   607  			t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response)
   608  		}
   609  		if test.Created != created {
   610  			t.Errorf("%d: expected created %t, got %t", i, test.Created, created)
   611  		}
   612  	}
   613  }
   614  
   615  type renegotiator struct {
   616  	called      bool
   617  	contentType string
   618  	params      map[string]string
   619  	decoder     runtime.Decoder
   620  	err         error
   621  }
   622  
   623  func (r *renegotiator) Decoder(contentType string, params map[string]string) (runtime.Decoder, error) {
   624  	r.called = true
   625  	r.contentType = contentType
   626  	r.params = params
   627  	return r.decoder, r.err
   628  }
   629  
   630  func (r *renegotiator) Encoder(contentType string, params map[string]string) (runtime.Encoder, error) {
   631  	return nil, fmt.Errorf("UNIMPLEMENTED")
   632  }
   633  
   634  func (r *renegotiator) StreamDecoder(contentType string, params map[string]string) (runtime.Decoder, runtime.Serializer, runtime.Framer, error) {
   635  	return nil, nil, nil, fmt.Errorf("UNIMPLEMENTED")
   636  }
   637  
   638  func TestTransformResponseNegotiate(t *testing.T) {
   639  	invalid := []byte("aaaaa")
   640  	uri, _ := url.Parse("http://localhost")
   641  	testCases := []struct {
   642  		Response *http.Response
   643  		Data     []byte
   644  		Created  bool
   645  		Error    bool
   646  		ErrFn    func(err error) bool
   647  
   648  		ContentType       string
   649  		Called            bool
   650  		ExpectContentType string
   651  		Decoder           runtime.Decoder
   652  		NegotiateErr      error
   653  	}{
   654  		{
   655  			ContentType: "application/json",
   656  			Response: &http.Response{
   657  				StatusCode: http.StatusUnauthorized,
   658  				Header:     http.Header{"Content-Type": []string{"application/json"}},
   659  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   660  			},
   661  			Called:            true,
   662  			ExpectContentType: "application/json",
   663  			Error:             true,
   664  			ErrFn: func(err error) bool {
   665  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   666  			},
   667  		},
   668  		{
   669  			ContentType: "application/json",
   670  			Response: &http.Response{
   671  				StatusCode: http.StatusUnauthorized,
   672  				Header:     http.Header{"Content-Type": []string{"application/protobuf"}},
   673  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   674  			},
   675  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   676  
   677  			Called:            true,
   678  			ExpectContentType: "application/protobuf",
   679  
   680  			Error: true,
   681  			ErrFn: func(err error) bool {
   682  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   683  			},
   684  		},
   685  		{
   686  			ContentType: "application/json",
   687  			Response: &http.Response{
   688  				StatusCode: http.StatusInternalServerError,
   689  				Header:     http.Header{"Content-Type": []string{"application/,others"}},
   690  			},
   691  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   692  
   693  			Error: true,
   694  			ErrFn: func(err error) bool {
   695  				return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500
   696  			},
   697  		},
   698  		{
   699  			// negotiate when no content type specified
   700  			Response: &http.Response{
   701  				StatusCode: http.StatusOK,
   702  				Header:     http.Header{"Content-Type": []string{"text/any"}},
   703  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   704  			},
   705  			Decoder:           scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   706  			Called:            true,
   707  			ExpectContentType: "text/any",
   708  		},
   709  		{
   710  			// negotiate when no response content type specified
   711  			ContentType: "text/any",
   712  			Response: &http.Response{
   713  				StatusCode: http.StatusOK,
   714  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   715  			},
   716  			Decoder:           scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   717  			Called:            true,
   718  			ExpectContentType: "text/any",
   719  		},
   720  		{
   721  			// unrecognized content type is not handled
   722  			ContentType: "application/json",
   723  			Response: &http.Response{
   724  				StatusCode: http.StatusNotFound,
   725  				Header:     http.Header{"Content-Type": []string{"application/unrecognized"}},
   726  				Body:       ioutil.NopCloser(bytes.NewReader(invalid)),
   727  			},
   728  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   729  
   730  			NegotiateErr:      fmt.Errorf("aaaa"),
   731  			Called:            true,
   732  			ExpectContentType: "application/unrecognized",
   733  
   734  			Error: true,
   735  			ErrFn: func(err error) bool {
   736  				return err.Error() != "aaaaa" && apierrors.IsNotFound(err)
   737  			},
   738  		},
   739  	}
   740  	for i, test := range testCases {
   741  		contentConfig := defaultContentConfig()
   742  		contentConfig.ContentType = test.ContentType
   743  		negotiator := &renegotiator{
   744  			decoder: test.Decoder,
   745  			err:     test.NegotiateErr,
   746  		}
   747  		contentConfig.Negotiator = negotiator
   748  		r := NewRequestWithClient(uri, "", contentConfig, nil)
   749  		if test.Response.Body == nil {
   750  			test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
   751  		}
   752  		result := r.transformResponse(test.Response, &http.Request{})
   753  		_, err := result.body, result.err
   754  		hasErr := err != nil
   755  		if hasErr != test.Error {
   756  			t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)
   757  			continue
   758  		} else if hasErr && test.Response.StatusCode > 399 {
   759  			status, ok := err.(apierrors.APIStatus)
   760  			if !ok {
   761  				t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err)
   762  				continue
   763  			}
   764  			if int(status.Status().Code) != test.Response.StatusCode {
   765  				t.Errorf("%d: status code did not match response: %#v", i, status.Status())
   766  			}
   767  		}
   768  		if test.ErrFn != nil && !test.ErrFn(err) {
   769  			t.Errorf("%d: error function did not match: %v", i, err)
   770  		}
   771  		if negotiator.called != test.Called {
   772  			t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called)
   773  		}
   774  		if !test.Called {
   775  			continue
   776  		}
   777  		if negotiator.contentType != test.ExpectContentType {
   778  			t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType)
   779  		}
   780  	}
   781  }
   782  
   783  func TestTransformUnstructuredError(t *testing.T) {
   784  	testCases := []struct {
   785  		Req *http.Request
   786  		Res *http.Response
   787  
   788  		Resource string
   789  		Name     string
   790  
   791  		ErrFn       func(error) bool
   792  		Transformed error
   793  	}{
   794  		{
   795  			Resource: "foo",
   796  			Name:     "bar",
   797  			Req: &http.Request{
   798  				Method: "POST",
   799  			},
   800  			Res: &http.Response{
   801  				StatusCode: http.StatusConflict,
   802  				Body:       ioutil.NopCloser(bytes.NewReader(nil)),
   803  			},
   804  			ErrFn: apierrors.IsAlreadyExists,
   805  		},
   806  		{
   807  			Resource: "foo",
   808  			Name:     "bar",
   809  			Req: &http.Request{
   810  				Method: "PUT",
   811  			},
   812  			Res: &http.Response{
   813  				StatusCode: http.StatusConflict,
   814  				Body:       ioutil.NopCloser(bytes.NewReader(nil)),
   815  			},
   816  			ErrFn: apierrors.IsConflict,
   817  		},
   818  		{
   819  			Resource: "foo",
   820  			Name:     "bar",
   821  			Req:      &http.Request{},
   822  			Res: &http.Response{
   823  				StatusCode: http.StatusNotFound,
   824  				Body:       ioutil.NopCloser(bytes.NewReader(nil)),
   825  			},
   826  			ErrFn: apierrors.IsNotFound,
   827  		},
   828  		{
   829  			Req: &http.Request{},
   830  			Res: &http.Response{
   831  				StatusCode: http.StatusBadRequest,
   832  				Body:       ioutil.NopCloser(bytes.NewReader(nil)),
   833  			},
   834  			ErrFn: apierrors.IsBadRequest,
   835  		},
   836  		{
   837  			// status in response overrides transformed result
   838  			Req:   &http.Request{},
   839  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))},
   840  			ErrFn: apierrors.IsBadRequest,
   841  			Transformed: &apierrors.StatusError{
   842  				ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
   843  			},
   844  		},
   845  		{
   846  			// successful status is ignored
   847  			Req:   &http.Request{},
   848  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))},
   849  			ErrFn: apierrors.IsBadRequest,
   850  		},
   851  		{
   852  			// empty object does not change result
   853  			Req:   &http.Request{},
   854  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))},
   855  			ErrFn: apierrors.IsBadRequest,
   856  		},
   857  		{
   858  			// we default apiVersion for backwards compatibility with old clients
   859  			// TODO: potentially remove in 1.7
   860  			Req:   &http.Request{},
   861  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))},
   862  			ErrFn: apierrors.IsBadRequest,
   863  			Transformed: &apierrors.StatusError{
   864  				ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
   865  			},
   866  		},
   867  		{
   868  			// we do not default kind
   869  			Req:   &http.Request{},
   870  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))},
   871  			ErrFn: apierrors.IsBadRequest,
   872  		},
   873  	}
   874  
   875  	for _, testCase := range testCases {
   876  		t.Run("", func(t *testing.T) {
   877  			r := &Request{
   878  				c: &RESTClient{
   879  					content: defaultContentConfig(),
   880  				},
   881  				resourceName: testCase.Name,
   882  				resource:     testCase.Resource,
   883  			}
   884  			result := r.transformResponse(testCase.Res, testCase.Req)
   885  			err := result.err
   886  			if !testCase.ErrFn(err) {
   887  				t.Fatalf("unexpected error: %v", err)
   888  			}
   889  			if !apierrors.IsUnexpectedServerError(err) {
   890  				t.Errorf("unexpected error type: %v", err)
   891  			}
   892  			if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) {
   893  				t.Errorf("unexpected error string: %s", err)
   894  			}
   895  			if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) {
   896  				t.Errorf("unexpected error string: %s", err)
   897  			}
   898  
   899  			// verify Error() properly transforms the error
   900  			transformed := result.Error()
   901  			expect := testCase.Transformed
   902  			if expect == nil {
   903  				expect = err
   904  			}
   905  			if !reflect.DeepEqual(expect, transformed) {
   906  				t.Errorf("unexpected Error(): %s", diff.ObjectReflectDiff(expect, transformed))
   907  			}
   908  
   909  			// verify result.Get properly transforms the error
   910  			if _, err := result.Get(); !reflect.DeepEqual(expect, err) {
   911  				t.Errorf("unexpected error on Get(): %s", diff.ObjectReflectDiff(expect, err))
   912  			}
   913  
   914  			// verify result.Into properly handles the error
   915  			if err := result.Into(&v1.Pod{}); !reflect.DeepEqual(expect, err) {
   916  				t.Errorf("unexpected error on Into(): %s", diff.ObjectReflectDiff(expect, err))
   917  			}
   918  
   919  			// verify result.Raw leaves the error in the untransformed state
   920  			if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) {
   921  				t.Errorf("unexpected error on Raw(): %s", diff.ObjectReflectDiff(expect, err))
   922  			}
   923  		})
   924  	}
   925  }
   926  
   927  func TestRequestWatch(t *testing.T) {
   928  	testCases := []struct {
   929  		name             string
   930  		Request          *Request
   931  		maxRetries       int
   932  		serverReturns    []responseErr
   933  		Expect           []watch.Event
   934  		attemptsExpected int
   935  		Err              bool
   936  		ErrFn            func(error) bool
   937  		Empty            bool
   938  	}{
   939  		{
   940  			name:             "Request has error",
   941  			Request:          &Request{err: errors.New("bail")},
   942  			attemptsExpected: 0,
   943  			Err:              true,
   944  		},
   945  		{
   946  			name:    "Client is nil, should use http.DefaultClient",
   947  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
   948  			Err:     true,
   949  		},
   950  		{
   951  			name: "error is not retryable",
   952  			Request: &Request{
   953  				c: &RESTClient{
   954  					base: &url.URL{},
   955  				},
   956  			},
   957  			serverReturns: []responseErr{
   958  				{response: nil, err: errors.New("err")},
   959  			},
   960  			attemptsExpected: 1,
   961  			Err:              true,
   962  		},
   963  		{
   964  			name: "server returns forbidden",
   965  			Request: &Request{
   966  				c: &RESTClient{
   967  					content: defaultContentConfig(),
   968  					base:    &url.URL{},
   969  				},
   970  			},
   971  			serverReturns: []responseErr{
   972  				{response: &http.Response{
   973  					StatusCode: http.StatusForbidden,
   974  					Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
   975  				}, err: nil},
   976  			},
   977  			attemptsExpected: 1,
   978  			Expect: []watch.Event{
   979  				{
   980  					Type: watch.Error,
   981  					Object: &metav1.Status{
   982  						Status:  "Failure",
   983  						Code:    500,
   984  						Reason:  "InternalError",
   985  						Message: `an error on the server ("unable to decode an event from the watch stream: test error") has prevented the request from succeeding`,
   986  						Details: &metav1.StatusDetails{
   987  							Causes: []metav1.StatusCause{
   988  								{
   989  									Type:    "UnexpectedServerResponse",
   990  									Message: "unable to decode an event from the watch stream: test error",
   991  								},
   992  								{
   993  									Type:    "ClientWatchDecoding",
   994  									Message: "unable to decode an event from the watch stream: test error",
   995  								},
   996  							},
   997  						},
   998  					},
   999  				},
  1000  			},
  1001  			Err: true,
  1002  			ErrFn: func(err error) bool {
  1003  				return apierrors.IsForbidden(err)
  1004  			},
  1005  		},
  1006  		{
  1007  			name: "server returns forbidden",
  1008  			Request: &Request{
  1009  				c: &RESTClient{
  1010  					content: defaultContentConfig(),
  1011  					base:    &url.URL{},
  1012  				},
  1013  			},
  1014  			serverReturns: []responseErr{
  1015  				{response: &http.Response{
  1016  					StatusCode: http.StatusForbidden,
  1017  					Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
  1018  				}, err: nil},
  1019  			},
  1020  			attemptsExpected: 1,
  1021  			Err:              true,
  1022  			ErrFn: func(err error) bool {
  1023  				return apierrors.IsForbidden(err)
  1024  			},
  1025  		},
  1026  		{
  1027  			name: "server returns unauthorized",
  1028  			Request: &Request{
  1029  				c: &RESTClient{
  1030  					content: defaultContentConfig(),
  1031  					base:    &url.URL{},
  1032  				},
  1033  			},
  1034  			serverReturns: []responseErr{
  1035  				{response: &http.Response{
  1036  					StatusCode: http.StatusUnauthorized,
  1037  					Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
  1038  				}, err: nil},
  1039  			},
  1040  			attemptsExpected: 1,
  1041  			Err:              true,
  1042  			ErrFn: func(err error) bool {
  1043  				return apierrors.IsUnauthorized(err)
  1044  			},
  1045  		},
  1046  		{
  1047  			name: "server returns unauthorized",
  1048  			Request: &Request{
  1049  				c: &RESTClient{
  1050  					content: defaultContentConfig(),
  1051  					base:    &url.URL{},
  1052  				},
  1053  			},
  1054  			serverReturns: []responseErr{
  1055  				{response: &http.Response{
  1056  					StatusCode: http.StatusUnauthorized,
  1057  					Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
  1058  						Status: metav1.StatusFailure,
  1059  						Reason: metav1.StatusReasonUnauthorized,
  1060  					})))),
  1061  				}, err: nil},
  1062  			},
  1063  			attemptsExpected: 1,
  1064  			Err:              true,
  1065  			ErrFn: func(err error) bool {
  1066  				return apierrors.IsUnauthorized(err)
  1067  			},
  1068  		},
  1069  		{
  1070  			name: "server returns EOF error",
  1071  			Request: &Request{
  1072  				c: &RESTClient{
  1073  					base: &url.URL{},
  1074  				},
  1075  			},
  1076  			serverReturns: []responseErr{
  1077  				{response: nil, err: io.EOF},
  1078  			},
  1079  			attemptsExpected: 1,
  1080  			Empty:            true,
  1081  		},
  1082  		{
  1083  			name: "server returns can't write HTTP request on broken connection error",
  1084  			Request: &Request{
  1085  				c: &RESTClient{
  1086  					base: &url.URL{},
  1087  				},
  1088  			},
  1089  			serverReturns: []responseErr{
  1090  				{response: nil, err: errors.New("http: can't write HTTP request on broken connection")},
  1091  			},
  1092  			attemptsExpected: 1,
  1093  			Empty:            true,
  1094  		},
  1095  		{
  1096  			name: "server returns connection reset by peer",
  1097  			Request: &Request{
  1098  				c: &RESTClient{
  1099  					base: &url.URL{},
  1100  				},
  1101  			},
  1102  			serverReturns: []responseErr{
  1103  				{response: nil, err: errors.New("foo: connection reset by peer")},
  1104  			},
  1105  			attemptsExpected: 1,
  1106  			Empty:            true,
  1107  		},
  1108  		{
  1109  			name: "max retries 2, server always returns EOF error",
  1110  			Request: &Request{
  1111  				c: &RESTClient{
  1112  					base: &url.URL{},
  1113  				},
  1114  			},
  1115  			maxRetries:       2,
  1116  			attemptsExpected: 3,
  1117  			serverReturns: []responseErr{
  1118  				{response: nil, err: io.EOF},
  1119  				{response: nil, err: io.EOF},
  1120  				{response: nil, err: io.EOF},
  1121  			},
  1122  			Empty: true,
  1123  		},
  1124  		{
  1125  			name: "max retries 1, server returns a retry-after response, request body seek error",
  1126  			Request: &Request{
  1127  				body: &readSeeker{err: io.EOF},
  1128  				c: &RESTClient{
  1129  					base: &url.URL{},
  1130  				},
  1131  			},
  1132  			maxRetries:       1,
  1133  			attemptsExpected: 1,
  1134  			serverReturns: []responseErr{
  1135  				{response: retryAfterResponse(), err: nil},
  1136  			},
  1137  			Err: true,
  1138  			ErrFn: func(err error) bool {
  1139  				return apierrors.IsInternalError(err)
  1140  			},
  1141  		},
  1142  		{
  1143  			name: "max retries 1, server returns a retryable error, request body seek error",
  1144  			Request: &Request{
  1145  				body: &readSeeker{err: io.EOF},
  1146  				c: &RESTClient{
  1147  					base: &url.URL{},
  1148  				},
  1149  			},
  1150  			maxRetries:       1,
  1151  			attemptsExpected: 1,
  1152  			serverReturns: []responseErr{
  1153  				{response: nil, err: io.EOF},
  1154  			},
  1155  			Empty: true,
  1156  		},
  1157  		{
  1158  			name: "max retries 2, server always returns a response with Retry-After header",
  1159  			Request: &Request{
  1160  				c: &RESTClient{
  1161  					base: &url.URL{},
  1162  				},
  1163  			},
  1164  			maxRetries:       2,
  1165  			attemptsExpected: 3,
  1166  			serverReturns: []responseErr{
  1167  				{response: retryAfterResponse(), err: nil},
  1168  				{response: retryAfterResponse(), err: nil},
  1169  				{response: retryAfterResponse(), err: nil},
  1170  			},
  1171  			Err: true,
  1172  			ErrFn: func(err error) bool {
  1173  				return apierrors.IsInternalError(err)
  1174  			},
  1175  		},
  1176  	}
  1177  
  1178  	for _, testCase := range testCases {
  1179  		t.Run(testCase.name, func(t *testing.T) {
  1180  			var attemptsGot int
  1181  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  1182  				defer func() {
  1183  					attemptsGot++
  1184  				}()
  1185  
  1186  				if attemptsGot >= len(testCase.serverReturns) {
  1187  					t.Fatalf("Wrong test setup, the server does not know what to return")
  1188  				}
  1189  				re := testCase.serverReturns[attemptsGot]
  1190  				return re.response, re.err
  1191  			})
  1192  			if c := testCase.Request.c; c != nil && len(testCase.serverReturns) > 0 {
  1193  				c.Client = client
  1194  			}
  1195  			testCase.Request.backoff = &noSleepBackOff{}
  1196  			testCase.Request.retry = &withRetry{maxRetries: testCase.maxRetries}
  1197  
  1198  			watch, err := testCase.Request.Watch(context.Background())
  1199  
  1200  			if watch == nil && err == nil {
  1201  				t.Fatal("Both watch.Interface and err returned by Watch are nil")
  1202  			}
  1203  			if testCase.attemptsExpected != attemptsGot {
  1204  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", testCase.attemptsExpected, attemptsGot)
  1205  			}
  1206  			hasErr := err != nil
  1207  			if hasErr != testCase.Err {
  1208  				t.Fatalf("expected %t, got %t: %v", testCase.Err, hasErr, err)
  1209  			}
  1210  			if testCase.ErrFn != nil && !testCase.ErrFn(err) {
  1211  				t.Errorf("error not valid: %v", err)
  1212  			}
  1213  			if hasErr && watch != nil {
  1214  				t.Fatalf("watch should be nil when error is returned")
  1215  			}
  1216  			if hasErr {
  1217  				return
  1218  			}
  1219  			defer watch.Stop()
  1220  			if testCase.Empty {
  1221  				evt, ok := <-watch.ResultChan()
  1222  				if ok {
  1223  					t.Errorf("expected the watch to be empty: %#v", evt)
  1224  				}
  1225  			}
  1226  			if testCase.Expect != nil {
  1227  				for i, evt := range testCase.Expect {
  1228  					out, ok := <-watch.ResultChan()
  1229  					if !ok {
  1230  						t.Fatalf("Watch closed early, %d/%d read", i, len(testCase.Expect))
  1231  					}
  1232  					if !reflect.DeepEqual(evt, out) {
  1233  						t.Fatalf("Event %d does not match: %s", i, diff.ObjectReflectDiff(evt, out))
  1234  					}
  1235  				}
  1236  			}
  1237  		})
  1238  	}
  1239  }
  1240  
  1241  func TestRequestStream(t *testing.T) {
  1242  	testCases := []struct {
  1243  		name             string
  1244  		Request          *Request
  1245  		maxRetries       int
  1246  		serverReturns    []responseErr
  1247  		attemptsExpected int
  1248  		Err              bool
  1249  		ErrFn            func(error) bool
  1250  	}{
  1251  		{
  1252  			name:             "request has error",
  1253  			Request:          &Request{err: errors.New("bail")},
  1254  			attemptsExpected: 0,
  1255  			Err:              true,
  1256  		},
  1257  		{
  1258  			name:    "Client is nil, should use http.DefaultClient",
  1259  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
  1260  			Err:     true,
  1261  		},
  1262  		{
  1263  			name: "server returns an error",
  1264  			Request: &Request{
  1265  				c: &RESTClient{
  1266  					base: &url.URL{},
  1267  				},
  1268  			},
  1269  			serverReturns: []responseErr{
  1270  				{response: nil, err: errors.New("err")},
  1271  			},
  1272  			attemptsExpected: 1,
  1273  			Err:              true,
  1274  		},
  1275  		{
  1276  			Request: &Request{
  1277  				c: &RESTClient{
  1278  					content: defaultContentConfig(),
  1279  					base:    &url.URL{},
  1280  				},
  1281  			},
  1282  			serverReturns: []responseErr{
  1283  				{response: &http.Response{
  1284  					StatusCode: http.StatusUnauthorized,
  1285  					Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
  1286  						Status: metav1.StatusFailure,
  1287  						Reason: metav1.StatusReasonUnauthorized,
  1288  					})))),
  1289  				}, err: nil},
  1290  			},
  1291  			attemptsExpected: 1,
  1292  			Err:              true,
  1293  		},
  1294  		{
  1295  			Request: &Request{
  1296  				c: &RESTClient{
  1297  					content: defaultContentConfig(),
  1298  					base:    &url.URL{},
  1299  				},
  1300  			},
  1301  			serverReturns: []responseErr{
  1302  				{response: &http.Response{
  1303  					StatusCode: http.StatusBadRequest,
  1304  					Body:       ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]","reason":"BadRequest","code":400}`))),
  1305  				}, err: nil},
  1306  			},
  1307  			attemptsExpected: 1,
  1308  			Err:              true,
  1309  			ErrFn: func(err error) bool {
  1310  				if err.Error() == "a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]" {
  1311  					return true
  1312  				}
  1313  				return false
  1314  			},
  1315  		},
  1316  		{
  1317  			name: "max retries 1, server returns a retry-after response, request body seek error",
  1318  			Request: &Request{
  1319  				body: &readSeeker{err: io.EOF},
  1320  				c: &RESTClient{
  1321  					base: &url.URL{},
  1322  				},
  1323  			},
  1324  			maxRetries:       1,
  1325  			attemptsExpected: 1,
  1326  			serverReturns: []responseErr{
  1327  				{response: retryAfterResponse(), err: nil},
  1328  			},
  1329  			Err: true,
  1330  			ErrFn: func(err error) bool {
  1331  				return apierrors.IsInternalError(err)
  1332  			},
  1333  		},
  1334  		{
  1335  			name: "max retries 2, server always returns a response with Retry-After header",
  1336  			Request: &Request{
  1337  				c: &RESTClient{
  1338  					base: &url.URL{},
  1339  				},
  1340  			},
  1341  			maxRetries:       2,
  1342  			attemptsExpected: 3,
  1343  			serverReturns: []responseErr{
  1344  				{response: retryAfterResponse(), err: nil},
  1345  				{response: retryAfterResponse(), err: nil},
  1346  				{response: retryAfterResponse(), err: nil},
  1347  			},
  1348  			Err: true,
  1349  			ErrFn: func(err error) bool {
  1350  				return apierrors.IsInternalError(err)
  1351  			},
  1352  		},
  1353  		{
  1354  			name: "server returns EOF after attempt 1, retry aborted",
  1355  			Request: &Request{
  1356  				c: &RESTClient{
  1357  					base: &url.URL{},
  1358  				},
  1359  			},
  1360  			maxRetries:       2,
  1361  			attemptsExpected: 2,
  1362  			serverReturns: []responseErr{
  1363  				{response: retryAfterResponse(), err: nil},
  1364  				{response: nil, err: io.EOF},
  1365  			},
  1366  			Err: true,
  1367  			ErrFn: func(err error) bool {
  1368  				return unWrap(err) == io.EOF
  1369  			},
  1370  		},
  1371  		{
  1372  			name: "max retries 2, server returns success on the final attempt",
  1373  			Request: &Request{
  1374  				c: &RESTClient{
  1375  					base: &url.URL{},
  1376  				},
  1377  			},
  1378  			maxRetries:       2,
  1379  			attemptsExpected: 3,
  1380  			serverReturns: []responseErr{
  1381  				{response: retryAfterResponse(), err: nil},
  1382  				{response: retryAfterResponse(), err: nil},
  1383  				{response: &http.Response{
  1384  					StatusCode: http.StatusOK,
  1385  					Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
  1386  				}, err: nil},
  1387  			},
  1388  		},
  1389  	}
  1390  
  1391  	for _, testCase := range testCases {
  1392  		t.Run(testCase.name, func(t *testing.T) {
  1393  			var attemptsGot int
  1394  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  1395  				defer func() {
  1396  					attemptsGot++
  1397  				}()
  1398  
  1399  				if attemptsGot >= len(testCase.serverReturns) {
  1400  					t.Fatalf("Wrong test setup, the server does not know what to return")
  1401  				}
  1402  				re := testCase.serverReturns[attemptsGot]
  1403  				return re.response, re.err
  1404  			})
  1405  			if c := testCase.Request.c; c != nil && len(testCase.serverReturns) > 0 {
  1406  				c.Client = client
  1407  			}
  1408  			testCase.Request.backoff = &noSleepBackOff{}
  1409  			testCase.Request.retry = &withRetry{maxRetries: testCase.maxRetries}
  1410  
  1411  			body, err := testCase.Request.Stream(context.Background())
  1412  
  1413  			if body == nil && err == nil {
  1414  				t.Fatal("Both body and err returned by Stream are nil")
  1415  			}
  1416  			if testCase.attemptsExpected != attemptsGot {
  1417  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", testCase.attemptsExpected, attemptsGot)
  1418  			}
  1419  
  1420  			hasErr := err != nil
  1421  			if hasErr != testCase.Err {
  1422  				t.Errorf("expected %t, got %t: %v", testCase.Err, hasErr, err)
  1423  			}
  1424  			if hasErr && body != nil {
  1425  				t.Error("body should be nil when error is returned")
  1426  			}
  1427  
  1428  			if hasErr {
  1429  				if testCase.ErrFn != nil && !testCase.ErrFn(err) {
  1430  					t.Errorf("unexpected error: %#v", err)
  1431  				}
  1432  			}
  1433  		})
  1434  	}
  1435  }
  1436  
  1437  type fakeUpgradeConnection struct{}
  1438  
  1439  func (c *fakeUpgradeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) {
  1440  	return nil, nil
  1441  }
  1442  func (c *fakeUpgradeConnection) Close() error {
  1443  	return nil
  1444  }
  1445  func (c *fakeUpgradeConnection) CloseChan() <-chan bool {
  1446  	return make(chan bool)
  1447  }
  1448  func (c *fakeUpgradeConnection) SetIdleTimeout(timeout time.Duration) {
  1449  }
  1450  
  1451  type fakeUpgradeRoundTripper struct {
  1452  	req  *http.Request
  1453  	conn httpstream.Connection
  1454  }
  1455  
  1456  func (f *fakeUpgradeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  1457  	f.req = req
  1458  	b := []byte{}
  1459  	body := ioutil.NopCloser(bytes.NewReader(b))
  1460  	resp := &http.Response{
  1461  		StatusCode: http.StatusSwitchingProtocols,
  1462  		Body:       body,
  1463  	}
  1464  	return resp, nil
  1465  }
  1466  
  1467  func (f *fakeUpgradeRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
  1468  	return f.conn, nil
  1469  }
  1470  
  1471  func TestRequestDo(t *testing.T) {
  1472  	testCases := []struct {
  1473  		Request *Request
  1474  		Err     bool
  1475  	}{
  1476  		{
  1477  			Request: &Request{c: &RESTClient{}, err: errors.New("bail")},
  1478  			Err:     true,
  1479  		},
  1480  		{
  1481  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
  1482  			Err:     true,
  1483  		},
  1484  		{
  1485  			Request: &Request{
  1486  				c: &RESTClient{
  1487  					Client: clientForFunc(func(req *http.Request) (*http.Response, error) {
  1488  						return nil, errors.New("err")
  1489  					}),
  1490  					base: &url.URL{},
  1491  				},
  1492  			},
  1493  			Err: true,
  1494  		},
  1495  	}
  1496  	for i, testCase := range testCases {
  1497  		testCase.Request.backoff = &NoBackoff{}
  1498  		testCase.Request.retry = &withRetry{}
  1499  		body, err := testCase.Request.Do(context.Background()).Raw()
  1500  		hasErr := err != nil
  1501  		if hasErr != testCase.Err {
  1502  			t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err)
  1503  		}
  1504  		if hasErr && body != nil {
  1505  			t.Errorf("%d: body should be nil when error is returned", i)
  1506  		}
  1507  	}
  1508  }
  1509  
  1510  func TestDoRequestNewWay(t *testing.T) {
  1511  	reqBody := "request body"
  1512  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1513  		Protocol:   "TCP",
  1514  		Port:       12345,
  1515  		TargetPort: intstr.FromInt(12345),
  1516  	}}}}
  1517  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1518  	fakeHandler := utiltesting.FakeHandler{
  1519  		StatusCode:   200,
  1520  		ResponseBody: string(expectedBody),
  1521  		T:            t,
  1522  	}
  1523  	testServer := httptest.NewServer(&fakeHandler)
  1524  	defer testServer.Close()
  1525  	c := testRESTClient(t, testServer)
  1526  	obj, err := c.Verb("POST").
  1527  		Prefix("foo", "bar").
  1528  		Suffix("baz").
  1529  		Timeout(time.Second).
  1530  		Body([]byte(reqBody)).
  1531  		Do(context.Background()).Get()
  1532  	if err != nil {
  1533  		t.Errorf("Unexpected error: %v %#v", err, err)
  1534  		return
  1535  	}
  1536  	if obj == nil {
  1537  		t.Error("nil obj")
  1538  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1539  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1540  	}
  1541  	requestURL := defaultResourcePathWithPrefix("foo/bar", "", "", "baz")
  1542  	requestURL += "?timeout=1s"
  1543  	fakeHandler.ValidateRequest(t, requestURL, "POST", &reqBody)
  1544  }
  1545  
  1546  // This test assumes that the client implementation backs off exponentially, for an individual request.
  1547  func TestBackoffLifecycle(t *testing.T) {
  1548  	count := 0
  1549  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1550  		count++
  1551  		t.Logf("Attempt %d", count)
  1552  		if count == 5 || count == 9 {
  1553  			w.WriteHeader(http.StatusOK)
  1554  			return
  1555  		}
  1556  		w.WriteHeader(http.StatusGatewayTimeout)
  1557  		return
  1558  	}))
  1559  	defer testServer.Close()
  1560  	c := testRESTClient(t, testServer)
  1561  
  1562  	// Test backoff recovery and increase.  This correlates to the constants
  1563  	// which are used in the server implementation returning StatusOK above.
  1564  	seconds := []int{0, 1, 2, 4, 8, 0, 1, 2, 4, 0}
  1565  	request := c.Verb("POST").Prefix("backofftest").Suffix("abc")
  1566  	clock := clock.FakeClock{}
  1567  	request.backoff = &URLBackoff{
  1568  		// Use a fake backoff here to avoid flakes and speed the test up.
  1569  		Backoff: flowcontrol.NewFakeBackOff(
  1570  			time.Duration(1)*time.Second,
  1571  			time.Duration(200)*time.Second,
  1572  			&clock,
  1573  		)}
  1574  
  1575  	for _, sec := range seconds {
  1576  		thisBackoff := request.backoff.CalculateBackoff(request.URL())
  1577  		t.Logf("Current backoff %v", thisBackoff)
  1578  		if thisBackoff != time.Duration(sec)*time.Second {
  1579  			t.Errorf("Backoff is %v instead of %v", thisBackoff, sec)
  1580  		}
  1581  		now := clock.Now()
  1582  		request.DoRaw(context.Background())
  1583  		elapsed := clock.Since(now)
  1584  		if clock.Since(now) != thisBackoff {
  1585  			t.Errorf("CalculatedBackoff not honored by clock: Expected time of %v, but got %v ", thisBackoff, elapsed)
  1586  		}
  1587  	}
  1588  }
  1589  
  1590  type testBackoffManager struct {
  1591  	sleeps []time.Duration
  1592  }
  1593  
  1594  func (b *testBackoffManager) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) {
  1595  }
  1596  
  1597  func (b *testBackoffManager) CalculateBackoff(actualUrl *url.URL) time.Duration {
  1598  	return time.Duration(0)
  1599  }
  1600  
  1601  func (b *testBackoffManager) Sleep(d time.Duration) {
  1602  	b.sleeps = append(b.sleeps, d)
  1603  }
  1604  
  1605  func TestCheckRetryClosesBody(t *testing.T) {
  1606  	count := 0
  1607  	ch := make(chan struct{})
  1608  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1609  		count++
  1610  		t.Logf("attempt %d", count)
  1611  		if count >= 5 {
  1612  			w.WriteHeader(http.StatusOK)
  1613  			close(ch)
  1614  			return
  1615  		}
  1616  		w.Header().Set("Retry-After", "1")
  1617  		http.Error(w, "Too many requests, please try again later.", http.StatusTooManyRequests)
  1618  	}))
  1619  	defer testServer.Close()
  1620  
  1621  	backoff := &testBackoffManager{}
  1622  	expectedSleeps := []time.Duration{0, time.Second, 0, time.Second, 0, time.Second, 0, time.Second, 0}
  1623  
  1624  	c := testRESTClient(t, testServer)
  1625  	c.createBackoffMgr = func() BackoffManager { return backoff }
  1626  	_, err := c.Verb("POST").
  1627  		Prefix("foo", "bar").
  1628  		Suffix("baz").
  1629  		Timeout(time.Second).
  1630  		Body([]byte(strings.Repeat("abcd", 1000))).
  1631  		DoRaw(context.Background())
  1632  	if err != nil {
  1633  		t.Fatalf("Unexpected error: %v %#v", err, err)
  1634  	}
  1635  	<-ch
  1636  	if count != 5 {
  1637  		t.Errorf("unexpected retries: %d", count)
  1638  	}
  1639  	if !reflect.DeepEqual(backoff.sleeps, expectedSleeps) {
  1640  		t.Errorf("unexpected sleeps, expected: %v, got: %v", expectedSleeps, backoff.sleeps)
  1641  	}
  1642  }
  1643  
  1644  func TestConnectionResetByPeerIsRetried(t *testing.T) {
  1645  	count := 0
  1646  	backoff := &testBackoffManager{}
  1647  	req := &Request{
  1648  		verb: "GET",
  1649  		c: &RESTClient{
  1650  			Client: clientForFunc(func(req *http.Request) (*http.Response, error) {
  1651  				count++
  1652  				if count >= 3 {
  1653  					return &http.Response{
  1654  						StatusCode: http.StatusOK,
  1655  						Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
  1656  					}, nil
  1657  				}
  1658  				return nil, &net.OpError{Err: syscall.ECONNRESET}
  1659  			}),
  1660  		},
  1661  		backoff: backoff,
  1662  		retry:   &withRetry{maxRetries: 10},
  1663  	}
  1664  	// We expect two retries of "connection reset by peer" and the success.
  1665  	_, err := req.Do(context.Background()).Raw()
  1666  	if err != nil {
  1667  		t.Errorf("Unexpected error: %v", err)
  1668  	}
  1669  	// We have a sleep before each retry (including the initial one) and for
  1670  	// every "retry-after" call - thus 5 together.
  1671  	if len(backoff.sleeps) != 5 {
  1672  		t.Errorf("Expected 5 retries, got: %d", len(backoff.sleeps))
  1673  	}
  1674  }
  1675  
  1676  func TestCheckRetryHandles429And5xx(t *testing.T) {
  1677  	count := 0
  1678  	ch := make(chan struct{})
  1679  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1680  		data, err := ioutil.ReadAll(req.Body)
  1681  		if err != nil {
  1682  			t.Fatalf("unable to read request body: %v", err)
  1683  		}
  1684  		if !bytes.Equal(data, []byte(strings.Repeat("abcd", 1000))) {
  1685  			t.Fatalf("retry did not send a complete body: %s", data)
  1686  		}
  1687  		t.Logf("attempt %d", count)
  1688  		if count >= 4 {
  1689  			w.WriteHeader(http.StatusOK)
  1690  			close(ch)
  1691  			return
  1692  		}
  1693  		w.Header().Set("Retry-After", "0")
  1694  		w.WriteHeader([]int{http.StatusTooManyRequests, 500, 501, 504}[count])
  1695  		count++
  1696  	}))
  1697  	defer testServer.Close()
  1698  
  1699  	c := testRESTClient(t, testServer)
  1700  	_, err := c.Verb("POST").
  1701  		Prefix("foo", "bar").
  1702  		Suffix("baz").
  1703  		Timeout(time.Second).
  1704  		Body([]byte(strings.Repeat("abcd", 1000))).
  1705  		DoRaw(context.Background())
  1706  	if err != nil {
  1707  		t.Fatalf("Unexpected error: %v %#v", err, err)
  1708  	}
  1709  	<-ch
  1710  	if count != 4 {
  1711  		t.Errorf("unexpected retries: %d", count)
  1712  	}
  1713  }
  1714  
  1715  func BenchmarkCheckRetryClosesBody(b *testing.B) {
  1716  	count := 0
  1717  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1718  		count++
  1719  		if count%3 == 0 {
  1720  			w.WriteHeader(http.StatusOK)
  1721  			return
  1722  		}
  1723  		w.Header().Set("Retry-After", "0")
  1724  		w.WriteHeader(http.StatusTooManyRequests)
  1725  	}))
  1726  	defer testServer.Close()
  1727  
  1728  	c := testRESTClient(b, testServer)
  1729  
  1730  	requests := make([]*Request, 0, b.N)
  1731  	for i := 0; i < b.N; i++ {
  1732  		requests = append(requests, c.Verb("POST").
  1733  			Prefix("foo", "bar").
  1734  			Suffix("baz").
  1735  			Timeout(time.Second).
  1736  			Body([]byte(strings.Repeat("abcd", 1000))))
  1737  	}
  1738  
  1739  	b.ResetTimer()
  1740  	for i := 0; i < b.N; i++ {
  1741  		if _, err := requests[i].DoRaw(context.Background()); err != nil {
  1742  			b.Fatalf("Unexpected error (%d/%d): %v", i, b.N, err)
  1743  		}
  1744  	}
  1745  }
  1746  
  1747  func TestDoRequestNewWayReader(t *testing.T) {
  1748  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1749  	reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1750  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1751  		Protocol:   "TCP",
  1752  		Port:       12345,
  1753  		TargetPort: intstr.FromInt(12345),
  1754  	}}}}
  1755  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1756  	fakeHandler := utiltesting.FakeHandler{
  1757  		StatusCode:   200,
  1758  		ResponseBody: string(expectedBody),
  1759  		T:            t,
  1760  	}
  1761  	testServer := httptest.NewServer(&fakeHandler)
  1762  	defer testServer.Close()
  1763  	c := testRESTClient(t, testServer)
  1764  	obj, err := c.Verb("POST").
  1765  		Resource("bar").
  1766  		Name("baz").
  1767  		Prefix("foo").
  1768  		Timeout(time.Second).
  1769  		Body(bytes.NewBuffer(reqBodyExpected)).
  1770  		Do(context.Background()).Get()
  1771  	if err != nil {
  1772  		t.Errorf("Unexpected error: %v %#v", err, err)
  1773  		return
  1774  	}
  1775  	if obj == nil {
  1776  		t.Error("nil obj")
  1777  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1778  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1779  	}
  1780  	tmpStr := string(reqBodyExpected)
  1781  	requestURL := defaultResourcePathWithPrefix("foo", "bar", "", "baz")
  1782  	requestURL += "?timeout=1s"
  1783  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1784  }
  1785  
  1786  func TestDoRequestNewWayObj(t *testing.T) {
  1787  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1788  	reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1789  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1790  		Protocol:   "TCP",
  1791  		Port:       12345,
  1792  		TargetPort: intstr.FromInt(12345),
  1793  	}}}}
  1794  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1795  	fakeHandler := utiltesting.FakeHandler{
  1796  		StatusCode:   200,
  1797  		ResponseBody: string(expectedBody),
  1798  		T:            t,
  1799  	}
  1800  	testServer := httptest.NewServer(&fakeHandler)
  1801  	defer testServer.Close()
  1802  	c := testRESTClient(t, testServer)
  1803  	obj, err := c.Verb("POST").
  1804  		Suffix("baz").
  1805  		Name("bar").
  1806  		Resource("foo").
  1807  		Timeout(time.Second).
  1808  		Body(reqObj).
  1809  		Do(context.Background()).Get()
  1810  	if err != nil {
  1811  		t.Errorf("Unexpected error: %v %#v", err, err)
  1812  		return
  1813  	}
  1814  	if obj == nil {
  1815  		t.Error("nil obj")
  1816  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1817  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1818  	}
  1819  	tmpStr := string(reqBodyExpected)
  1820  	requestURL := defaultResourcePathWithPrefix("", "foo", "", "bar/baz")
  1821  	requestURL += "?timeout=1s"
  1822  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1823  }
  1824  
  1825  func TestDoRequestNewWayFile(t *testing.T) {
  1826  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1827  	reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1828  	if err != nil {
  1829  		t.Errorf("unexpected error: %v", err)
  1830  	}
  1831  
  1832  	file, err := ioutil.TempFile("", "foo")
  1833  	if err != nil {
  1834  		t.Errorf("unexpected error: %v", err)
  1835  	}
  1836  	defer file.Close()
  1837  	defer os.Remove(file.Name())
  1838  
  1839  	_, err = file.Write(reqBodyExpected)
  1840  	if err != nil {
  1841  		t.Errorf("unexpected error: %v", err)
  1842  	}
  1843  
  1844  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1845  		Protocol:   "TCP",
  1846  		Port:       12345,
  1847  		TargetPort: intstr.FromInt(12345),
  1848  	}}}}
  1849  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1850  	fakeHandler := utiltesting.FakeHandler{
  1851  		StatusCode:   200,
  1852  		ResponseBody: string(expectedBody),
  1853  		T:            t,
  1854  	}
  1855  	testServer := httptest.NewServer(&fakeHandler)
  1856  	defer testServer.Close()
  1857  	c := testRESTClient(t, testServer)
  1858  	wasCreated := true
  1859  	obj, err := c.Verb("POST").
  1860  		Prefix("foo/bar", "baz").
  1861  		Timeout(time.Second).
  1862  		Body(file.Name()).
  1863  		Do(context.Background()).WasCreated(&wasCreated).Get()
  1864  	if err != nil {
  1865  		t.Errorf("Unexpected error: %v %#v", err, err)
  1866  		return
  1867  	}
  1868  	if obj == nil {
  1869  		t.Error("nil obj")
  1870  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1871  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1872  	}
  1873  	if wasCreated {
  1874  		t.Errorf("expected object was created")
  1875  	}
  1876  	tmpStr := string(reqBodyExpected)
  1877  	requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "")
  1878  	requestURL += "?timeout=1s"
  1879  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1880  }
  1881  
  1882  func TestWasCreated(t *testing.T) {
  1883  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1884  	reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1885  	if err != nil {
  1886  		t.Errorf("unexpected error: %v", err)
  1887  	}
  1888  
  1889  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1890  		Protocol:   "TCP",
  1891  		Port:       12345,
  1892  		TargetPort: intstr.FromInt(12345),
  1893  	}}}}
  1894  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1895  	fakeHandler := utiltesting.FakeHandler{
  1896  		StatusCode:   201,
  1897  		ResponseBody: string(expectedBody),
  1898  		T:            t,
  1899  	}
  1900  	testServer := httptest.NewServer(&fakeHandler)
  1901  	defer testServer.Close()
  1902  	c := testRESTClient(t, testServer)
  1903  	wasCreated := false
  1904  	obj, err := c.Verb("PUT").
  1905  		Prefix("foo/bar", "baz").
  1906  		Timeout(time.Second).
  1907  		Body(reqBodyExpected).
  1908  		Do(context.Background()).WasCreated(&wasCreated).Get()
  1909  	if err != nil {
  1910  		t.Errorf("Unexpected error: %v %#v", err, err)
  1911  		return
  1912  	}
  1913  	if obj == nil {
  1914  		t.Error("nil obj")
  1915  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1916  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1917  	}
  1918  	if !wasCreated {
  1919  		t.Errorf("Expected object was created")
  1920  	}
  1921  
  1922  	tmpStr := string(reqBodyExpected)
  1923  	requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "")
  1924  	requestURL += "?timeout=1s"
  1925  	fakeHandler.ValidateRequest(t, requestURL, "PUT", &tmpStr)
  1926  }
  1927  
  1928  func TestVerbs(t *testing.T) {
  1929  	c := testRESTClient(t, nil)
  1930  	if r := c.Post(); r.verb != "POST" {
  1931  		t.Errorf("Post verb is wrong")
  1932  	}
  1933  	if r := c.Put(); r.verb != "PUT" {
  1934  		t.Errorf("Put verb is wrong")
  1935  	}
  1936  	if r := c.Get(); r.verb != "GET" {
  1937  		t.Errorf("Get verb is wrong")
  1938  	}
  1939  	if r := c.Delete(); r.verb != "DELETE" {
  1940  		t.Errorf("Delete verb is wrong")
  1941  	}
  1942  }
  1943  
  1944  func TestAbsPath(t *testing.T) {
  1945  	for i, tc := range []struct {
  1946  		configPrefix   string
  1947  		resourcePrefix string
  1948  		absPath        string
  1949  		wantsAbsPath   string
  1950  	}{
  1951  		{"/", "", "", "/"},
  1952  		{"", "", "/", "/"},
  1953  		{"", "", "/api", "/api"},
  1954  		{"", "", "/api/", "/api/"},
  1955  		{"", "", "/apis", "/apis"},
  1956  		{"", "/foo", "/bar/foo", "/bar/foo"},
  1957  		{"", "/api/foo/123", "/bar/foo", "/bar/foo"},
  1958  		{"/p1", "", "", "/p1"},
  1959  		{"/p1", "", "/", "/p1/"},
  1960  		{"/p1", "", "/api", "/p1/api"},
  1961  		{"/p1", "", "/apis", "/p1/apis"},
  1962  		{"/p1", "/r1", "/apis", "/p1/apis"},
  1963  		{"/p1", "/api/r1", "/apis", "/p1/apis"},
  1964  		{"/p1/api/p2", "", "", "/p1/api/p2"},
  1965  		{"/p1/api/p2", "", "/", "/p1/api/p2/"},
  1966  		{"/p1/api/p2", "", "/api", "/p1/api/p2/api"},
  1967  		{"/p1/api/p2", "", "/api/", "/p1/api/p2/api/"},
  1968  		{"/p1/api/p2", "/r1", "/api/", "/p1/api/p2/api/"},
  1969  		{"/p1/api/p2", "/api/r1", "/api/", "/p1/api/p2/api/"},
  1970  	} {
  1971  		u, _ := url.Parse("http://localhost:123" + tc.configPrefix)
  1972  		r := NewRequestWithClient(u, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").Prefix(tc.resourcePrefix).AbsPath(tc.absPath)
  1973  		if r.pathPrefix != tc.wantsAbsPath {
  1974  			t.Errorf("test case %d failed, unexpected path: %q, expected %q", i, r.pathPrefix, tc.wantsAbsPath)
  1975  		}
  1976  	}
  1977  }
  1978  
  1979  func TestUnacceptableParamNames(t *testing.T) {
  1980  	table := []struct {
  1981  		name          string
  1982  		testVal       string
  1983  		expectSuccess bool
  1984  	}{
  1985  		// timeout is no longer "protected"
  1986  		{"timeout", "42", true},
  1987  	}
  1988  
  1989  	for _, item := range table {
  1990  		c := testRESTClient(t, nil)
  1991  		r := c.Get().setParam(item.name, item.testVal)
  1992  		if e, a := item.expectSuccess, r.err == nil; e != a {
  1993  			t.Errorf("expected %v, got %v (%v)", e, a, r.err)
  1994  		}
  1995  	}
  1996  }
  1997  
  1998  func TestBody(t *testing.T) {
  1999  	const data = "test payload"
  2000  
  2001  	obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  2002  	bodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), obj)
  2003  
  2004  	f, err := ioutil.TempFile("", "test_body")
  2005  	if err != nil {
  2006  		t.Fatalf("TempFile error: %v", err)
  2007  	}
  2008  	if _, err := f.WriteString(data); err != nil {
  2009  		t.Fatalf("TempFile.WriteString error: %v", err)
  2010  	}
  2011  	f.Close()
  2012  	defer os.Remove(f.Name())
  2013  
  2014  	var nilObject *metav1.DeleteOptions
  2015  	typedObject := interface{}(nilObject)
  2016  	c := testRESTClient(t, nil)
  2017  	tests := []struct {
  2018  		input    interface{}
  2019  		expected string
  2020  		headers  map[string]string
  2021  	}{
  2022  		{[]byte(data), data, nil},
  2023  		{f.Name(), data, nil},
  2024  		{strings.NewReader(data), data, nil},
  2025  		{obj, string(bodyExpected), map[string]string{"Content-Type": "application/json"}},
  2026  		{typedObject, "", nil},
  2027  	}
  2028  	for i, tt := range tests {
  2029  		r := c.Post().Body(tt.input)
  2030  		if r.err != nil {
  2031  			t.Errorf("%d: r.Body(%#v) error: %v", i, tt, r.err)
  2032  			continue
  2033  		}
  2034  		if tt.headers != nil {
  2035  			for k, v := range tt.headers {
  2036  				if r.headers.Get(k) != v {
  2037  					t.Errorf("%d: r.headers[%q] = %q; want %q", i, k, v, v)
  2038  				}
  2039  			}
  2040  		}
  2041  
  2042  		if r.body == nil {
  2043  			if len(tt.expected) != 0 {
  2044  				t.Errorf("%d: r.body = %q; want %q", i, r.body, tt.expected)
  2045  			}
  2046  			continue
  2047  		}
  2048  		buf := make([]byte, len(tt.expected))
  2049  		if _, err := r.body.Read(buf); err != nil {
  2050  			t.Errorf("%d: r.body.Read error: %v", i, err)
  2051  			continue
  2052  		}
  2053  		body := string(buf)
  2054  		if body != tt.expected {
  2055  			t.Errorf("%d: r.body = %q; want %q", i, body, tt.expected)
  2056  		}
  2057  	}
  2058  }
  2059  
  2060  func TestWatch(t *testing.T) {
  2061  	tests := []struct {
  2062  		name       string
  2063  		maxRetries int
  2064  	}{
  2065  		{
  2066  			name:       "no retry",
  2067  			maxRetries: 0,
  2068  		},
  2069  		{
  2070  			name:       "with retries",
  2071  			maxRetries: 3,
  2072  		},
  2073  	}
  2074  
  2075  	for _, test := range tests {
  2076  		t.Run(test.name, func(t *testing.T) {
  2077  			var table = []struct {
  2078  				t   watch.EventType
  2079  				obj runtime.Object
  2080  			}{
  2081  				{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2082  				{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2083  				{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2084  			}
  2085  
  2086  			var attempts int
  2087  			testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2088  				defer func() {
  2089  					attempts++
  2090  				}()
  2091  
  2092  				flusher, ok := w.(http.Flusher)
  2093  				if !ok {
  2094  					panic("need flusher!")
  2095  				}
  2096  
  2097  				if attempts < test.maxRetries {
  2098  					w.Header().Set("Retry-After", "1")
  2099  					w.WriteHeader(http.StatusTooManyRequests)
  2100  					return
  2101  				}
  2102  
  2103  				w.Header().Set("Transfer-Encoding", "chunked")
  2104  				w.WriteHeader(http.StatusOK)
  2105  				flusher.Flush()
  2106  
  2107  				encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2108  				for _, item := range table {
  2109  					if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2110  						panic(err)
  2111  					}
  2112  					flusher.Flush()
  2113  				}
  2114  			}))
  2115  			defer testServer.Close()
  2116  
  2117  			s := testRESTClient(t, testServer)
  2118  			watching, err := s.Get().Prefix("path/to/watch/thing").
  2119  				MaxRetries(test.maxRetries).Watch(context.Background())
  2120  			if err != nil {
  2121  				t.Fatalf("Unexpected error: %v", err)
  2122  			}
  2123  
  2124  			for _, item := range table {
  2125  				got, ok := <-watching.ResultChan()
  2126  				if !ok {
  2127  					t.Fatalf("Unexpected early close")
  2128  				}
  2129  				if e, a := item.t, got.Type; e != a {
  2130  					t.Errorf("Expected %v, got %v", e, a)
  2131  				}
  2132  				if e, a := item.obj, got.Object; !apiequality.Semantic.DeepDerivative(e, a) {
  2133  					t.Errorf("Expected %v, got %v", e, a)
  2134  				}
  2135  			}
  2136  
  2137  			_, ok := <-watching.ResultChan()
  2138  			if ok {
  2139  				t.Fatal("Unexpected non-close")
  2140  			}
  2141  		})
  2142  	}
  2143  }
  2144  
  2145  func TestWatchNonDefaultContentType(t *testing.T) {
  2146  	var table = []struct {
  2147  		t   watch.EventType
  2148  		obj runtime.Object
  2149  	}{
  2150  		{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2151  		{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2152  		{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2153  	}
  2154  
  2155  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2156  		flusher, ok := w.(http.Flusher)
  2157  		if !ok {
  2158  			panic("need flusher!")
  2159  		}
  2160  
  2161  		w.Header().Set("Transfer-Encoding", "chunked")
  2162  		// manually set the content type here so we get the renegotiation behavior
  2163  		w.Header().Set("Content-Type", "application/json")
  2164  		w.WriteHeader(http.StatusOK)
  2165  		flusher.Flush()
  2166  
  2167  		encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2168  		for _, item := range table {
  2169  			if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2170  				panic(err)
  2171  			}
  2172  			flusher.Flush()
  2173  		}
  2174  	}))
  2175  	defer testServer.Close()
  2176  
  2177  	// set the default content type to protobuf so that we test falling back to JSON serialization
  2178  	contentConfig := defaultContentConfig()
  2179  	contentConfig.ContentType = "application/vnd.kubernetes.protobuf"
  2180  	s := testRESTClientWithConfig(t, testServer, contentConfig)
  2181  	watching, err := s.Get().Prefix("path/to/watch/thing").Watch(context.Background())
  2182  	if err != nil {
  2183  		t.Fatalf("Unexpected error")
  2184  	}
  2185  
  2186  	for _, item := range table {
  2187  		got, ok := <-watching.ResultChan()
  2188  		if !ok {
  2189  			t.Fatalf("Unexpected early close")
  2190  		}
  2191  		if e, a := item.t, got.Type; e != a {
  2192  			t.Errorf("Expected %v, got %v", e, a)
  2193  		}
  2194  		if e, a := item.obj, got.Object; !apiequality.Semantic.DeepDerivative(e, a) {
  2195  			t.Errorf("Expected %v, got %v", e, a)
  2196  		}
  2197  	}
  2198  
  2199  	_, ok := <-watching.ResultChan()
  2200  	if ok {
  2201  		t.Fatal("Unexpected non-close")
  2202  	}
  2203  }
  2204  
  2205  func TestWatchUnknownContentType(t *testing.T) {
  2206  	var table = []struct {
  2207  		t   watch.EventType
  2208  		obj runtime.Object
  2209  	}{
  2210  		{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2211  		{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2212  		{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2213  	}
  2214  
  2215  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2216  		flusher, ok := w.(http.Flusher)
  2217  		if !ok {
  2218  			panic("need flusher!")
  2219  		}
  2220  
  2221  		w.Header().Set("Transfer-Encoding", "chunked")
  2222  		// manually set the content type here so we get the renegotiation behavior
  2223  		w.Header().Set("Content-Type", "foobar")
  2224  		w.WriteHeader(http.StatusOK)
  2225  		flusher.Flush()
  2226  
  2227  		encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2228  		for _, item := range table {
  2229  			if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2230  				panic(err)
  2231  			}
  2232  			flusher.Flush()
  2233  		}
  2234  	}))
  2235  	defer testServer.Close()
  2236  
  2237  	s := testRESTClient(t, testServer)
  2238  	_, err := s.Get().Prefix("path/to/watch/thing").Watch(context.Background())
  2239  	if err == nil {
  2240  		t.Fatalf("Expected to fail due to lack of known stream serialization for content type")
  2241  	}
  2242  }
  2243  
  2244  func TestStream(t *testing.T) {
  2245  	expectedBody := "expected body"
  2246  
  2247  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2248  		flusher, ok := w.(http.Flusher)
  2249  		if !ok {
  2250  			panic("need flusher!")
  2251  		}
  2252  		w.Header().Set("Transfer-Encoding", "chunked")
  2253  		w.WriteHeader(http.StatusOK)
  2254  		w.Write([]byte(expectedBody))
  2255  		flusher.Flush()
  2256  	}))
  2257  	defer testServer.Close()
  2258  
  2259  	s := testRESTClient(t, testServer)
  2260  	readCloser, err := s.Get().Prefix("path/to/stream/thing").Stream(context.Background())
  2261  	if err != nil {
  2262  		t.Fatalf("unexpected error: %v", err)
  2263  	}
  2264  	defer readCloser.Close()
  2265  	buf := new(bytes.Buffer)
  2266  	buf.ReadFrom(readCloser)
  2267  	resultBody := buf.String()
  2268  
  2269  	if expectedBody != resultBody {
  2270  		t.Errorf("Expected %s, got %s", expectedBody, resultBody)
  2271  	}
  2272  }
  2273  
  2274  func testRESTClientWithConfig(t testing.TB, srv *httptest.Server, contentConfig ClientContentConfig) *RESTClient {
  2275  	base, _ := url.Parse("http://localhost")
  2276  	if srv != nil {
  2277  		var err error
  2278  		base, err = url.Parse(srv.URL)
  2279  		if err != nil {
  2280  			t.Fatalf("failed to parse test URL: %v", err)
  2281  		}
  2282  	}
  2283  	versionedAPIPath := defaultResourcePathWithPrefix("", "", "", "")
  2284  	client, err := NewRESTClient(base, versionedAPIPath, contentConfig, nil, nil)
  2285  	if err != nil {
  2286  		t.Fatalf("failed to create a client: %v", err)
  2287  	}
  2288  	return client
  2289  
  2290  }
  2291  
  2292  func testRESTClient(t testing.TB, srv *httptest.Server) *RESTClient {
  2293  	contentConfig := defaultContentConfig()
  2294  	return testRESTClientWithConfig(t, srv, contentConfig)
  2295  }
  2296  
  2297  func TestDoContext(t *testing.T) {
  2298  	receivedCh := make(chan struct{})
  2299  	block := make(chan struct{})
  2300  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  2301  		close(receivedCh)
  2302  		<-block
  2303  		w.WriteHeader(http.StatusOK)
  2304  	}))
  2305  	defer testServer.Close()
  2306  	defer close(block)
  2307  
  2308  	ctx, cancel := context.WithCancel(context.Background())
  2309  	defer cancel()
  2310  
  2311  	go func() {
  2312  		<-receivedCh
  2313  		cancel()
  2314  	}()
  2315  
  2316  	c := testRESTClient(t, testServer)
  2317  	_, err := c.Verb("GET").
  2318  		Prefix("foo").
  2319  		DoRaw(ctx)
  2320  	if err == nil {
  2321  		t.Fatal("Expected context cancellation error")
  2322  	}
  2323  }
  2324  
  2325  func buildString(length int) string {
  2326  	s := make([]byte, length)
  2327  	for i := range s {
  2328  		s[i] = 'a'
  2329  	}
  2330  	return string(s)
  2331  }
  2332  
  2333  func init() {
  2334  	klog.InitFlags(nil)
  2335  }
  2336  
  2337  func TestTruncateBody(t *testing.T) {
  2338  	tests := []struct {
  2339  		body  string
  2340  		want  string
  2341  		level string
  2342  	}{
  2343  		// Anything below 8 is completely truncated
  2344  		{
  2345  			body:  "Completely truncated below 8",
  2346  			want:  " [truncated 28 chars]",
  2347  			level: "0",
  2348  		},
  2349  		// Small strings are not truncated by high levels
  2350  		{
  2351  			body:  "Small body never gets truncated",
  2352  			want:  "Small body never gets truncated",
  2353  			level: "10",
  2354  		},
  2355  		{
  2356  			body:  "Small body never gets truncated",
  2357  			want:  "Small body never gets truncated",
  2358  			level: "8",
  2359  		},
  2360  		// Strings are truncated to 1024 if level is less than 9.
  2361  		{
  2362  			body:  buildString(2000),
  2363  			level: "8",
  2364  			want:  fmt.Sprintf("%s [truncated 976 chars]", buildString(1024)),
  2365  		},
  2366  		// Strings are truncated to 10240 if level is 9.
  2367  		{
  2368  			body:  buildString(20000),
  2369  			level: "9",
  2370  			want:  fmt.Sprintf("%s [truncated 9760 chars]", buildString(10240)),
  2371  		},
  2372  		// Strings are not truncated if level is 10 or higher
  2373  		{
  2374  			body:  buildString(20000),
  2375  			level: "10",
  2376  			want:  buildString(20000),
  2377  		},
  2378  		// Strings are not truncated if level is 10 or higher
  2379  		{
  2380  			body:  buildString(20000),
  2381  			level: "11",
  2382  			want:  buildString(20000),
  2383  		},
  2384  	}
  2385  
  2386  	l := flag.Lookup("v").Value.(flag.Getter).Get().(klog.Level)
  2387  	for _, test := range tests {
  2388  		flag.Set("v", test.level)
  2389  		got := truncateBody(test.body)
  2390  		if got != test.want {
  2391  			t.Errorf("truncateBody(%v) = %v, want %v", test.body, got, test.want)
  2392  		}
  2393  	}
  2394  	flag.Set("v", l.String())
  2395  }
  2396  
  2397  func defaultResourcePathWithPrefix(prefix, resource, namespace, name string) string {
  2398  	var path string
  2399  	path = "/api/" + v1.SchemeGroupVersion.Version
  2400  
  2401  	if prefix != "" {
  2402  		path = path + "/" + prefix
  2403  	}
  2404  	if namespace != "" {
  2405  		path = path + "/namespaces/" + namespace
  2406  	}
  2407  	// Resource names are lower case.
  2408  	resource = strings.ToLower(resource)
  2409  	if resource != "" {
  2410  		path = path + "/" + resource
  2411  	}
  2412  	if name != "" {
  2413  		path = path + "/" + name
  2414  	}
  2415  	return path
  2416  }
  2417  
  2418  func TestRequestPreflightCheck(t *testing.T) {
  2419  	for _, tt := range []struct {
  2420  		name         string
  2421  		verbs        []string
  2422  		namespace    string
  2423  		resourceName string
  2424  		namespaceSet bool
  2425  		expectsErr   bool
  2426  	}{
  2427  		{
  2428  			name:         "no namespace set",
  2429  			verbs:        []string{"GET", "PUT", "DELETE", "POST"},
  2430  			namespaceSet: false,
  2431  			expectsErr:   false,
  2432  		},
  2433  		{
  2434  			name:         "empty resource name and namespace",
  2435  			verbs:        []string{"GET", "PUT", "DELETE"},
  2436  			namespaceSet: true,
  2437  			expectsErr:   false,
  2438  		},
  2439  		{
  2440  			name:         "resource name with empty namespace",
  2441  			verbs:        []string{"GET", "PUT", "DELETE"},
  2442  			namespaceSet: true,
  2443  			resourceName: "ResourceName",
  2444  			expectsErr:   true,
  2445  		},
  2446  		{
  2447  			name:         "post empty resource name and namespace",
  2448  			verbs:        []string{"POST"},
  2449  			namespaceSet: true,
  2450  			expectsErr:   true,
  2451  		},
  2452  		{
  2453  			name:         "working requests",
  2454  			verbs:        []string{"GET", "PUT", "DELETE", "POST"},
  2455  			namespaceSet: true,
  2456  			resourceName: "ResourceName",
  2457  			namespace:    "Namespace",
  2458  			expectsErr:   false,
  2459  		},
  2460  	} {
  2461  		t.Run(tt.name, func(t *testing.T) {
  2462  			for _, verb := range tt.verbs {
  2463  				r := &Request{
  2464  					verb:         verb,
  2465  					namespace:    tt.namespace,
  2466  					resourceName: tt.resourceName,
  2467  					namespaceSet: tt.namespaceSet,
  2468  				}
  2469  
  2470  				err := r.requestPreflightCheck()
  2471  				hasErr := err != nil
  2472  				if hasErr == tt.expectsErr {
  2473  					return
  2474  				}
  2475  				t.Errorf("%s: expects error: %v, has error: %v", verb, tt.expectsErr, hasErr)
  2476  			}
  2477  		})
  2478  	}
  2479  }
  2480  
  2481  func TestThrottledLogger(t *testing.T) {
  2482  	now := time.Now()
  2483  	oldClock := globalThrottledLogger.clock
  2484  	defer func() {
  2485  		globalThrottledLogger.clock = oldClock
  2486  	}()
  2487  	clock := clock.NewFakeClock(now)
  2488  	globalThrottledLogger.clock = clock
  2489  
  2490  	logMessages := 0
  2491  	for i := 0; i < 1000; i++ {
  2492  		var wg sync.WaitGroup
  2493  		wg.Add(10)
  2494  		for j := 0; j < 10; j++ {
  2495  			go func() {
  2496  				if _, ok := globalThrottledLogger.attemptToLog(); ok {
  2497  					logMessages++
  2498  				}
  2499  				wg.Done()
  2500  			}()
  2501  		}
  2502  		wg.Wait()
  2503  		now = now.Add(1 * time.Second)
  2504  		clock.SetTime(now)
  2505  	}
  2506  
  2507  	if a, e := logMessages, 100; a != e {
  2508  		t.Fatalf("expected %v log messages, but got %v", e, a)
  2509  	}
  2510  }
  2511  
  2512  func TestRequestMaxRetries(t *testing.T) {
  2513  	successAtNthCalls := 1
  2514  	actualCalls := 0
  2515  	retryOneTimeHandler := func(w http.ResponseWriter, req *http.Request) {
  2516  		defer func() { actualCalls++ }()
  2517  		if actualCalls >= successAtNthCalls {
  2518  			w.WriteHeader(http.StatusOK)
  2519  			return
  2520  		}
  2521  		w.Header().Set("Retry-After", "1")
  2522  		w.WriteHeader(http.StatusTooManyRequests)
  2523  		actualCalls++
  2524  	}
  2525  	testServer := httptest.NewServer(http.HandlerFunc(retryOneTimeHandler))
  2526  	defer testServer.Close()
  2527  
  2528  	u, err := url.Parse(testServer.URL)
  2529  	if err != nil {
  2530  		t.Error(err)
  2531  	}
  2532  
  2533  	testCases := []struct {
  2534  		name        string
  2535  		maxRetries  int
  2536  		expectError bool
  2537  	}{
  2538  		{
  2539  			name:        "no retrying should fail",
  2540  			maxRetries:  0,
  2541  			expectError: true,
  2542  		},
  2543  		{
  2544  			name:        "1 max-retry should exactly work",
  2545  			maxRetries:  1,
  2546  			expectError: false,
  2547  		},
  2548  		{
  2549  			name:        "5 max-retry should work",
  2550  			maxRetries:  5,
  2551  			expectError: false,
  2552  		},
  2553  	}
  2554  
  2555  	for _, testCase := range testCases {
  2556  		t.Run(testCase.name, func(t *testing.T) {
  2557  			defer func() { actualCalls = 0 }()
  2558  			_, err := NewRequestWithClient(u, "", defaultContentConfig(), testServer.Client()).
  2559  				Verb("get").
  2560  				MaxRetries(testCase.maxRetries).
  2561  				AbsPath("/foo").
  2562  				DoRaw(context.TODO())
  2563  			hasError := err != nil
  2564  			if testCase.expectError != hasError {
  2565  				t.Error(" failed checking error")
  2566  			}
  2567  		})
  2568  	}
  2569  }
  2570  
  2571  type responseErr struct {
  2572  	response *http.Response
  2573  	err      error
  2574  }
  2575  
  2576  type seek struct {
  2577  	offset int64
  2578  	whence int
  2579  }
  2580  
  2581  type count struct {
  2582  	// keeps track of the number of Seek(offset, whence) calls.
  2583  	seeks []seek
  2584  
  2585  	// how many times {Request|Response}.Body.Close() has been invoked
  2586  	lock   sync.Mutex
  2587  	closes int
  2588  }
  2589  
  2590  func (c *count) close() {
  2591  	c.lock.Lock()
  2592  	defer c.lock.Unlock()
  2593  	c.closes++
  2594  }
  2595  func (c *count) getCloseCount() int {
  2596  	c.lock.Lock()
  2597  	defer c.lock.Unlock()
  2598  	return c.closes
  2599  }
  2600  
  2601  // used to track {Request|Response}.Body
  2602  type readTracker struct {
  2603  	delegated io.Reader
  2604  	count     *count
  2605  }
  2606  
  2607  func (r *readTracker) Seek(offset int64, whence int) (int64, error) {
  2608  	if seeker, ok := r.delegated.(io.Seeker); ok {
  2609  		r.count.seeks = append(r.count.seeks, seek{offset: offset, whence: whence})
  2610  		return seeker.Seek(offset, whence)
  2611  	}
  2612  	return 0, io.EOF
  2613  }
  2614  
  2615  func (r *readTracker) Read(p []byte) (n int, err error) {
  2616  	return r.delegated.Read(p)
  2617  }
  2618  
  2619  func (r *readTracker) Close() error {
  2620  	if closer, ok := r.delegated.(io.Closer); ok {
  2621  		r.count.close()
  2622  		return closer.Close()
  2623  	}
  2624  	return nil
  2625  }
  2626  
  2627  func newReadTracker(count *count) *readTracker {
  2628  	return &readTracker{
  2629  		count: count,
  2630  	}
  2631  }
  2632  
  2633  func newCount() *count {
  2634  	return &count{
  2635  		closes: 0,
  2636  		seeks:  make([]seek, 0),
  2637  	}
  2638  }
  2639  
  2640  type readSeeker struct{ err error }
  2641  
  2642  func (rs readSeeker) Read([]byte) (int, error)       { return 0, rs.err }
  2643  func (rs readSeeker) Seek(int64, int) (int64, error) { return 0, rs.err }
  2644  
  2645  func unWrap(err error) error {
  2646  	if uerr, ok := err.(*url.Error); ok {
  2647  		return uerr.Err
  2648  	}
  2649  	return err
  2650  }
  2651  
  2652  // noSleepBackOff is a NoBackoff except it does not sleep,
  2653  // used for faster execution of the unit tests.
  2654  type noSleepBackOff struct {
  2655  	*NoBackoff
  2656  }
  2657  
  2658  func (n *noSleepBackOff) Sleep(d time.Duration) {}
  2659  
  2660  func TestRequestWithRetry(t *testing.T) {
  2661  	tests := []struct {
  2662  		name                         string
  2663  		body                         io.Reader
  2664  		serverReturns                responseErr
  2665  		errExpected                  error
  2666  		transformFuncInvokedExpected int
  2667  		roundTripInvokedExpected     int
  2668  	}{
  2669  		{
  2670  			name:                         "server returns retry-after response, request body is not io.Seeker, retry goes ahead",
  2671  			body:                         ioutil.NopCloser(bytes.NewReader([]byte{})),
  2672  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2673  			errExpected:                  nil,
  2674  			transformFuncInvokedExpected: 1,
  2675  			roundTripInvokedExpected:     2,
  2676  		},
  2677  		{
  2678  			name:                         "server returns retry-after response, request body Seek returns error, retry aborted",
  2679  			body:                         &readSeeker{err: io.EOF},
  2680  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2681  			errExpected:                  nil,
  2682  			transformFuncInvokedExpected: 1,
  2683  			roundTripInvokedExpected:     1,
  2684  		},
  2685  		{
  2686  			name:                         "server returns retry-after response, request body Seek returns no error, retry goes ahead",
  2687  			body:                         &readSeeker{err: nil},
  2688  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2689  			errExpected:                  nil,
  2690  			transformFuncInvokedExpected: 1,
  2691  			roundTripInvokedExpected:     2,
  2692  		},
  2693  		{
  2694  			name:                         "server returns retryable err, request body is not io.Seek, retry goes ahead",
  2695  			body:                         ioutil.NopCloser(bytes.NewReader([]byte{})),
  2696  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2697  			errExpected:                  io.ErrUnexpectedEOF,
  2698  			transformFuncInvokedExpected: 0,
  2699  			roundTripInvokedExpected:     2,
  2700  		},
  2701  		{
  2702  			name:                         "server returns retryable err, request body Seek returns error, retry aborted",
  2703  			body:                         &readSeeker{err: io.EOF},
  2704  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2705  			errExpected:                  io.ErrUnexpectedEOF,
  2706  			transformFuncInvokedExpected: 0,
  2707  			roundTripInvokedExpected:     1,
  2708  		},
  2709  		{
  2710  			name:                         "server returns retryable err, request body Seek returns no err, retry goes ahead",
  2711  			body:                         &readSeeker{err: nil},
  2712  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2713  			errExpected:                  io.ErrUnexpectedEOF,
  2714  			transformFuncInvokedExpected: 0,
  2715  			roundTripInvokedExpected:     2,
  2716  		},
  2717  	}
  2718  
  2719  	for _, test := range tests {
  2720  		t.Run(test.name, func(t *testing.T) {
  2721  			var roundTripInvoked int
  2722  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  2723  				roundTripInvoked++
  2724  				return test.serverReturns.response, test.serverReturns.err
  2725  			})
  2726  
  2727  			req := &Request{
  2728  				verb: "GET",
  2729  				body: test.body,
  2730  				c: &RESTClient{
  2731  					Client: client,
  2732  				},
  2733  				backoff: &noSleepBackOff{},
  2734  				retry:   &withRetry{maxRetries: 1},
  2735  			}
  2736  
  2737  			var transformFuncInvoked int
  2738  			err := req.request(context.Background(), func(request *http.Request, response *http.Response) {
  2739  				transformFuncInvoked++
  2740  			})
  2741  
  2742  			if test.roundTripInvokedExpected != roundTripInvoked {
  2743  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", test.roundTripInvokedExpected, roundTripInvoked)
  2744  			}
  2745  			if test.transformFuncInvokedExpected != transformFuncInvoked {
  2746  				t.Errorf("Expected transform func to be invoked %d times, but got: %d", test.transformFuncInvokedExpected, transformFuncInvoked)
  2747  			}
  2748  			if test.errExpected != unWrap(err) {
  2749  				t.Errorf("Expected error: %v, but got: %v", test.errExpected, unWrap(err))
  2750  			}
  2751  		})
  2752  	}
  2753  }
  2754  
  2755  func TestRequestDoWithRetry(t *testing.T) {
  2756  	testRequestWithRetry(t, "Do", func(ctx context.Context, r *Request) {
  2757  		r.Do(ctx)
  2758  	})
  2759  }
  2760  
  2761  func TestRequestDoRawWithRetry(t *testing.T) {
  2762  	// both request.Do and request.DoRaw have the same behavior and expectations
  2763  	testRequestWithRetry(t, "Do", func(ctx context.Context, r *Request) {
  2764  		r.DoRaw(ctx)
  2765  	})
  2766  }
  2767  
  2768  func TestRequestStreamWithRetry(t *testing.T) {
  2769  	testRequestWithRetry(t, "Stream", func(ctx context.Context, r *Request) {
  2770  		r.Stream(ctx)
  2771  	})
  2772  }
  2773  
  2774  func TestRequestWatchWithRetry(t *testing.T) {
  2775  	testRequestWithRetry(t, "Watch", func(ctx context.Context, r *Request) {
  2776  		w, err := r.Watch(ctx)
  2777  		if err == nil {
  2778  			// in this test the the response body returned by the server is always empty,
  2779  			// this will cause StreamWatcher.receive() to:
  2780  			// - return an io.EOF to indicate that the watch closed normally and
  2781  			// - then close the io.Reader
  2782  			// since we assert on the number of times 'Close' has been called on the
  2783  			// body of the response object, we need to wait here to avoid race condition.
  2784  			<-w.ResultChan()
  2785  		}
  2786  	})
  2787  }
  2788  
  2789  func testRequestWithRetry(t *testing.T, key string, doFunc func(ctx context.Context, r *Request)) {
  2790  	type expected struct {
  2791  		attempts  int
  2792  		reqCount  *count
  2793  		respCount *count
  2794  	}
  2795  
  2796  	tests := []struct {
  2797  		name          string
  2798  		verb          string
  2799  		body          func() io.Reader
  2800  		maxRetries    int
  2801  		serverReturns []responseErr
  2802  
  2803  		// expectations differ based on whether it is 'Watch', 'Stream' or 'Do'
  2804  		expectations map[string]expected
  2805  	}{
  2806  		{
  2807  			name:       "server always returns retry-after response",
  2808  			verb:       "GET",
  2809  			body:       func() io.Reader { return bytes.NewReader([]byte{}) },
  2810  			maxRetries: 2,
  2811  			serverReturns: []responseErr{
  2812  				{response: retryAfterResponse(), err: nil},
  2813  				{response: retryAfterResponse(), err: nil},
  2814  				{response: retryAfterResponse(), err: nil},
  2815  			},
  2816  			expectations: map[string]expected{
  2817  				"Do": {
  2818  					attempts:  3,
  2819  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2820  					respCount: &count{closes: 3, seeks: []seek{}},
  2821  				},
  2822  				"Watch": {
  2823  					attempts:  3,
  2824  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2825  					respCount: &count{closes: 3, seeks: []seek{}},
  2826  				},
  2827  				"Stream": {
  2828  					attempts:  3,
  2829  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2830  					respCount: &count{closes: 3, seeks: []seek{}},
  2831  				},
  2832  			},
  2833  		},
  2834  		{
  2835  			name:       "server always returns retryable error",
  2836  			verb:       "GET",
  2837  			body:       func() io.Reader { return bytes.NewReader([]byte{}) },
  2838  			maxRetries: 2,
  2839  			serverReturns: []responseErr{
  2840  				{response: nil, err: io.EOF},
  2841  				{response: nil, err: io.EOF},
  2842  				{response: nil, err: io.EOF},
  2843  			},
  2844  			expectations: map[string]expected{
  2845  				"Do": {
  2846  					attempts:  3,
  2847  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2848  					respCount: &count{closes: 0, seeks: []seek{}},
  2849  				},
  2850  				"Watch": {
  2851  					attempts:  3,
  2852  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2853  					respCount: &count{closes: 0, seeks: []seek{}},
  2854  				},
  2855  				// for Stream, we never retry on any error
  2856  				"Stream": {
  2857  					attempts:  1, // only the first attempt is expected
  2858  					reqCount:  &count{closes: 0, seeks: []seek{}},
  2859  					respCount: &count{closes: 0, seeks: []seek{}},
  2860  				},
  2861  			},
  2862  		},
  2863  		{
  2864  			name:       "server returns success on the final retry",
  2865  			verb:       "GET",
  2866  			body:       func() io.Reader { return bytes.NewReader([]byte{}) },
  2867  			maxRetries: 2,
  2868  			serverReturns: []responseErr{
  2869  				{response: retryAfterResponse(), err: nil},
  2870  				{response: nil, err: io.EOF},
  2871  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  2872  			},
  2873  			expectations: map[string]expected{
  2874  				"Do": {
  2875  					attempts:  3,
  2876  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2877  					respCount: &count{closes: 2, seeks: []seek{}},
  2878  				},
  2879  				"Watch": {
  2880  					attempts: 3,
  2881  					reqCount: &count{closes: 0, seeks: make([]seek, 2)},
  2882  					// the Body of the successful response object will get closed by
  2883  					// StreamWatcher, so we need to take that into account.
  2884  					respCount: &count{closes: 2, seeks: []seek{}},
  2885  				},
  2886  				"Stream": {
  2887  					attempts:  2,
  2888  					reqCount:  &count{closes: 0, seeks: make([]seek, 1)},
  2889  					respCount: &count{closes: 1, seeks: []seek{}},
  2890  				},
  2891  			},
  2892  		},
  2893  	}
  2894  
  2895  	for _, test := range tests {
  2896  		t.Run(test.name, func(t *testing.T) {
  2897  			respCountGot := newCount()
  2898  			responseRecorder := newReadTracker(respCountGot)
  2899  			var attempts int
  2900  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  2901  				defer func() {
  2902  					attempts++
  2903  				}()
  2904  
  2905  				resp := test.serverReturns[attempts].response
  2906  				if resp != nil {
  2907  					responseRecorder.delegated = ioutil.NopCloser(bytes.NewReader([]byte{}))
  2908  					resp.Body = responseRecorder
  2909  				}
  2910  				return resp, test.serverReturns[attempts].err
  2911  			})
  2912  
  2913  			reqCountGot := newCount()
  2914  			reqRecorder := newReadTracker(reqCountGot)
  2915  			reqRecorder.delegated = test.body()
  2916  
  2917  			req := &Request{
  2918  				verb: test.verb,
  2919  				body: reqRecorder,
  2920  				c: &RESTClient{
  2921  					content: defaultContentConfig(),
  2922  					Client:  client,
  2923  				},
  2924  				backoff: &noSleepBackOff{},
  2925  				retry:   &withRetry{maxRetries: test.maxRetries},
  2926  			}
  2927  
  2928  			doFunc(context.Background(), req)
  2929  
  2930  			expected, ok := test.expectations[key]
  2931  			if !ok {
  2932  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  2933  			}
  2934  			if expected.attempts != attempts {
  2935  				t.Errorf("Expected retries: %d, but got: %d", expected.attempts, attempts)
  2936  			}
  2937  
  2938  			if !reflect.DeepEqual(expected.reqCount.seeks, reqCountGot.seeks) {
  2939  				t.Errorf("Expected request body to have seek invocation: %v, but got: %v", expected.reqCount.seeks, reqCountGot.seeks)
  2940  			}
  2941  			if expected.respCount.closes != respCountGot.getCloseCount() {
  2942  				t.Errorf("Expected response body Close to be invoked %d times, but got: %d", expected.respCount.closes, respCountGot.getCloseCount())
  2943  			}
  2944  		})
  2945  	}
  2946  }