k8s.io/client-go@v0.31.1/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  	"net"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"os"
    31  	"reflect"
    32  	"strings"
    33  	"sync"
    34  	"sync/atomic"
    35  	"syscall"
    36  	"testing"
    37  	"time"
    38  
    39  	"github.com/google/go-cmp/cmp"
    40  	v1 "k8s.io/api/core/v1"
    41  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    42  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    43  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    44  	"k8s.io/apimachinery/pkg/runtime"
    45  	"k8s.io/apimachinery/pkg/runtime/schema"
    46  	"k8s.io/apimachinery/pkg/runtime/serializer"
    47  	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
    48  	"k8s.io/apimachinery/pkg/util/intstr"
    49  	utilnet "k8s.io/apimachinery/pkg/util/net"
    50  	"k8s.io/apimachinery/pkg/watch"
    51  	"k8s.io/client-go/kubernetes/scheme"
    52  	restclientwatch "k8s.io/client-go/rest/watch"
    53  	"k8s.io/client-go/tools/metrics"
    54  	"k8s.io/client-go/util/flowcontrol"
    55  	utiltesting "k8s.io/client-go/util/testing"
    56  	"k8s.io/klog/v2"
    57  	testingclock "k8s.io/utils/clock/testing"
    58  )
    59  
    60  func TestNewRequestSetsAccept(t *testing.T) {
    61  	r := NewRequestWithClient(&url.URL{Path: "/path/"}, "", ClientContentConfig{}, nil).Verb("get")
    62  	if r.headers.Get("Accept") != "" {
    63  		t.Errorf("unexpected headers: %#v", r.headers)
    64  	}
    65  	r = NewRequestWithClient(&url.URL{Path: "/path/"}, "", ClientContentConfig{ContentType: "application/other"}, nil).Verb("get")
    66  	if r.headers.Get("Accept") != "application/other, */*" {
    67  		t.Errorf("unexpected headers: %#v", r.headers)
    68  	}
    69  }
    70  
    71  func clientForFunc(fn clientFunc) *http.Client {
    72  	return &http.Client{
    73  		Transport: fn,
    74  	}
    75  }
    76  
    77  type clientFunc func(req *http.Request) (*http.Response, error)
    78  
    79  func (f clientFunc) RoundTrip(req *http.Request) (*http.Response, error) {
    80  	return f(req)
    81  }
    82  
    83  func TestRequestSetsHeaders(t *testing.T) {
    84  	server := clientForFunc(func(req *http.Request) (*http.Response, error) {
    85  		if req.Header.Get("Accept") != "application/other, */*" {
    86  			t.Errorf("unexpected headers: %#v", req.Header)
    87  		}
    88  		return &http.Response{
    89  			StatusCode: http.StatusForbidden,
    90  			Body:       io.NopCloser(bytes.NewReader([]byte{})),
    91  		}, nil
    92  	})
    93  	config := defaultContentConfig()
    94  	config.ContentType = "application/other"
    95  	r := NewRequestWithClient(&url.URL{Path: "/path"}, "", config, nil).Verb("get")
    96  	r.c.Client = server
    97  
    98  	// Check if all "issue" methods are setting headers.
    99  	_ = r.Do(context.Background())
   100  	_, _ = r.Watch(context.Background())
   101  	_, _ = r.Stream(context.Background())
   102  }
   103  
   104  func TestRequestWithErrorWontChange(t *testing.T) {
   105  	gvCopy := v1.SchemeGroupVersion
   106  	original := Request{
   107  		err: errors.New("test"),
   108  		c: &RESTClient{
   109  			content: ClientContentConfig{GroupVersion: gvCopy},
   110  		},
   111  	}
   112  	r := original
   113  	changed := r.Param("foo", "bar").
   114  		AbsPath("/abs").
   115  		Prefix("test").
   116  		Suffix("testing").
   117  		Namespace("new").
   118  		Resource("foos").
   119  		Name("bars").
   120  		Body("foo").
   121  		Timeout(time.Millisecond)
   122  	if changed != &r {
   123  		t.Errorf("returned request should point to the same object")
   124  	}
   125  	if !reflect.DeepEqual(changed, &original) {
   126  		t.Errorf("expected %#v, got %#v", &original, changed)
   127  	}
   128  }
   129  
   130  func TestRequestPreservesBaseTrailingSlash(t *testing.T) {
   131  	r := &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "/path/"}
   132  	if s := r.URL().String(); s != "/path/" {
   133  		t.Errorf("trailing slash should be preserved: %s", s)
   134  	}
   135  }
   136  
   137  func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
   138  	r := (&Request{c: &RESTClient{base: &url.URL{}}}).AbsPath("/foo/")
   139  	if s := r.URL().String(); s != "/foo/" {
   140  		t.Errorf("trailing slash should be preserved: %s", s)
   141  	}
   142  }
   143  
   144  func TestRequestAbsPathJoins(t *testing.T) {
   145  	r := (&Request{c: &RESTClient{base: &url.URL{}}}).AbsPath("foo/bar", "baz")
   146  	if s := r.URL().String(); s != "foo/bar/baz" {
   147  		t.Errorf("trailing slash should be preserved: %s", s)
   148  	}
   149  }
   150  
   151  func TestRequestSetsNamespace(t *testing.T) {
   152  	r := (&Request{
   153  		c: &RESTClient{base: &url.URL{Path: "/"}},
   154  	}).Namespace("foo")
   155  	if r.namespace == "" {
   156  		t.Errorf("namespace should be set: %#v", r)
   157  	}
   158  
   159  	if s := r.URL().String(); s != "namespaces/foo" {
   160  		t.Errorf("namespace should be in path: %s", s)
   161  	}
   162  }
   163  
   164  func TestRequestOrdersNamespaceInPath(t *testing.T) {
   165  	r := (&Request{
   166  		c:          &RESTClient{base: &url.URL{}},
   167  		pathPrefix: "/test/",
   168  	}).Name("bar").Resource("baz").Namespace("foo")
   169  	if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar" {
   170  		t.Errorf("namespace should be in order in path: %s", s)
   171  	}
   172  }
   173  
   174  func TestRequestOrdersSubResource(t *testing.T) {
   175  	r := (&Request{
   176  		c:          &RESTClient{base: &url.URL{}},
   177  		pathPrefix: "/test/",
   178  	}).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b")
   179  	if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar/a/b/test" {
   180  		t.Errorf("namespace should be in order in path: %s", s)
   181  	}
   182  }
   183  
   184  func TestRequestSetTwiceError(t *testing.T) {
   185  	if (&Request{}).Name("bar").Name("baz").err == nil {
   186  		t.Errorf("setting name twice should result in error")
   187  	}
   188  	if (&Request{}).Namespace("bar").Namespace("baz").err == nil {
   189  		t.Errorf("setting namespace twice should result in error")
   190  	}
   191  	if (&Request{}).Resource("bar").Resource("baz").err == nil {
   192  		t.Errorf("setting resource twice should result in error")
   193  	}
   194  	if (&Request{}).SubResource("bar").SubResource("baz").err == nil {
   195  		t.Errorf("setting subresource twice should result in error")
   196  	}
   197  }
   198  
   199  func TestInvalidSegments(t *testing.T) {
   200  	invalidSegments := []string{".", "..", "test/segment", "test%2bsegment"}
   201  	setters := map[string]func(string, *Request){
   202  		"namespace":   func(s string, r *Request) { r.Namespace(s) },
   203  		"resource":    func(s string, r *Request) { r.Resource(s) },
   204  		"name":        func(s string, r *Request) { r.Name(s) },
   205  		"subresource": func(s string, r *Request) { r.SubResource(s) },
   206  	}
   207  	for _, invalidSegment := range invalidSegments {
   208  		for setterName, setter := range setters {
   209  			r := &Request{}
   210  			setter(invalidSegment, r)
   211  			if r.err == nil {
   212  				t.Errorf("%s: %s: expected error, got none", setterName, invalidSegment)
   213  			}
   214  		}
   215  	}
   216  }
   217  
   218  func TestRequestParam(t *testing.T) {
   219  	r := (&Request{}).Param("foo", "a")
   220  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) {
   221  		t.Errorf("should have set a param: %#v", r)
   222  	}
   223  
   224  	r.Param("bar", "1")
   225  	r.Param("bar", "2")
   226  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}, "bar": []string{"1", "2"}}) {
   227  		t.Errorf("should have set a param: %#v", r)
   228  	}
   229  }
   230  
   231  func TestRequestVersionedParams(t *testing.T) {
   232  	r := (&Request{c: &RESTClient{content: ClientContentConfig{GroupVersion: v1.SchemeGroupVersion}}}).Param("foo", "a")
   233  	if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) {
   234  		t.Errorf("should have set a param: %#v", r)
   235  	}
   236  	r.VersionedParams(&v1.PodLogOptions{Follow: true, Container: "bar"}, scheme.ParameterCodec)
   237  
   238  	if !reflect.DeepEqual(r.params, url.Values{
   239  		"foo":       []string{"a"},
   240  		"container": []string{"bar"},
   241  		"follow":    []string{"true"},
   242  	}) {
   243  		t.Errorf("should have set a param: %#v", r)
   244  	}
   245  }
   246  
   247  func TestRequestVersionedParamsFromListOptions(t *testing.T) {
   248  	r := &Request{c: &RESTClient{content: ClientContentConfig{GroupVersion: v1.SchemeGroupVersion}}}
   249  	r.VersionedParams(&metav1.ListOptions{ResourceVersion: "1"}, scheme.ParameterCodec)
   250  	if !reflect.DeepEqual(r.params, url.Values{
   251  		"resourceVersion": []string{"1"},
   252  	}) {
   253  		t.Errorf("should have set a param: %#v", r)
   254  	}
   255  
   256  	var timeout int64 = 10
   257  	r.VersionedParams(&metav1.ListOptions{ResourceVersion: "2", TimeoutSeconds: &timeout}, scheme.ParameterCodec)
   258  	if !reflect.DeepEqual(r.params, url.Values{
   259  		"resourceVersion": []string{"1", "2"},
   260  		"timeoutSeconds":  []string{"10"},
   261  	}) {
   262  		t.Errorf("should have set a param: %#v %v", r.params, r.err)
   263  	}
   264  }
   265  
   266  func TestRequestVersionedParamsWithInvalidScheme(t *testing.T) {
   267  	parameterCodec := runtime.NewParameterCodec(runtime.NewScheme())
   268  	r := (&Request{c: &RESTClient{content: ClientContentConfig{GroupVersion: v1.SchemeGroupVersion}}})
   269  	r.VersionedParams(&v1.PodExecOptions{Stdin: false, Stdout: true},
   270  		parameterCodec)
   271  
   272  	if r.Error() == nil {
   273  		t.Errorf("should have recorded an error: %#v", r.params)
   274  	}
   275  }
   276  
   277  func TestRequestError(t *testing.T) {
   278  	// Invalid body, see TestRequestBody()
   279  	r := (&Request{}).Body([]string{"test"})
   280  
   281  	if r.Error() != r.err {
   282  		t.Errorf("getter should be identical to reference: %#v %#v", r.Error(), r.err)
   283  	}
   284  }
   285  
   286  func TestRequestURI(t *testing.T) {
   287  	r := (&Request{}).Param("foo", "a")
   288  	r.Prefix("other")
   289  	r.RequestURI("/test?foo=b&a=b&c=1&c=2")
   290  	if r.pathPrefix != "/test" {
   291  		t.Errorf("path is wrong: %#v", r)
   292  	}
   293  	if !reflect.DeepEqual(r.params, url.Values{"a": []string{"b"}, "foo": []string{"b"}, "c": []string{"1", "2"}}) {
   294  		t.Errorf("should have set a param: %#v", r)
   295  	}
   296  }
   297  
   298  type NotAnAPIObject struct{}
   299  
   300  func (obj NotAnAPIObject) GroupVersionKind() *schema.GroupVersionKind       { return nil }
   301  func (obj NotAnAPIObject) SetGroupVersionKind(gvk *schema.GroupVersionKind) {}
   302  
   303  func defaultContentConfig() ClientContentConfig {
   304  	gvCopy := v1.SchemeGroupVersion
   305  	return ClientContentConfig{
   306  		ContentType:  "application/json",
   307  		GroupVersion: gvCopy,
   308  		Negotiator:   runtime.NewClientNegotiator(scheme.Codecs.WithoutConversion(), gvCopy),
   309  	}
   310  }
   311  
   312  func TestRequestBody(t *testing.T) {
   313  	// test unknown type
   314  	r := (&Request{}).Body([]string{"test"})
   315  	if r.err == nil || r.body != nil {
   316  		t.Errorf("should have set err and left body nil: %#v", r)
   317  	}
   318  
   319  	// test error set when failing to read file
   320  	f, err := os.CreateTemp("", "")
   321  	if err != nil {
   322  		t.Fatalf("unable to create temp file")
   323  	}
   324  	defer f.Close()
   325  	os.Remove(f.Name())
   326  	r = (&Request{}).Body(f.Name())
   327  	if r.err == nil || r.body != nil {
   328  		t.Errorf("should have set err and left body nil: %#v", r)
   329  	}
   330  
   331  	// test unencodable api object
   332  	r = (&Request{c: &RESTClient{content: defaultContentConfig()}}).Body(&NotAnAPIObject{})
   333  	if r.err == nil || r.body != nil {
   334  		t.Errorf("should have set err and left body nil: %#v", r)
   335  	}
   336  }
   337  
   338  func TestResultIntoWithErrReturnsErr(t *testing.T) {
   339  	res := Result{err: errors.New("test")}
   340  	if err := res.Into(&v1.Pod{}); err != res.err {
   341  		t.Errorf("should have returned exact error from result")
   342  	}
   343  }
   344  
   345  func TestResultIntoWithNoBodyReturnsErr(t *testing.T) {
   346  	res := Result{
   347  		body:    []byte{},
   348  		decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   349  	}
   350  	if err := res.Into(&v1.Pod{}); err == nil || !strings.Contains(err.Error(), "0-length") {
   351  		t.Errorf("should have complained about 0 length body")
   352  	}
   353  }
   354  
   355  func TestURLTemplate(t *testing.T) {
   356  	uri, _ := url.Parse("http://localhost/some/base/url/path")
   357  	uriSingleSlash, _ := url.Parse("http://localhost/")
   358  	testCases := []struct {
   359  		Request          *Request
   360  		ExpectedFullURL  string
   361  		ExpectedFinalURL string
   362  	}{
   363  		{
   364  			// non dynamic client
   365  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
   366  				Prefix("api", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
   367  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/nm?p0=v0",
   368  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D",
   369  		},
   370  		{
   371  			// non dynamic client with wrong api group
   372  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").
   373  				Prefix("pre1", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"),
   374  			ExpectedFullURL:  "http://localhost/some/base/url/path/pre1/v1/namespaces/ns/r1/nm?p0=v0",
   375  			ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
   376  		},
   377  		{
   378  			// dynamic client with core group + namespace + resourceResource (with name)
   379  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   380  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   381  				Prefix("/api/v1/namespaces/ns/r1/name1"),
   382  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/name1",
   383  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
   384  		},
   385  		{
   386  			// dynamic client with named group + namespace + resourceResource (with name)
   387  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   388  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   389  				Prefix("/apis/g1/v1/namespaces/ns/r1/name1"),
   390  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1/name1",
   391  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D",
   392  		},
   393  		{
   394  			// dynamic client with core group + namespace + resourceResource (with NO name)
   395  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
   396  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   397  				Prefix("/api/v1/namespaces/ns/r1"),
   398  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1",
   399  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1",
   400  		},
   401  		{
   402  			// dynamic client with named group + namespace + resourceResource (with NO name)
   403  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE
   404  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   405  				Prefix("/apis/g1/v1/namespaces/ns/r1"),
   406  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1",
   407  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1",
   408  		},
   409  		{
   410  			// dynamic client with core group + resourceResource (with name)
   411  			// /api/$RESOURCEVERSION/$RESOURCE/%NAME
   412  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   413  				Prefix("/api/v1/r1/name1"),
   414  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/r1/name1",
   415  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/r1/%7Bname%7D",
   416  		},
   417  		{
   418  			// dynamic client with named group + resourceResource (with name)
   419  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   420  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   421  				Prefix("/apis/g1/v1/r1/name1"),
   422  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/g1/v1/r1/name1",
   423  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/%7Bname%7D",
   424  		},
   425  		{
   426  			// dynamic client with named group + namespace + resourceResource (with name) + subresource
   427  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
   428  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   429  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   430  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   431  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D/finalize",
   432  		},
   433  		{
   434  			// dynamic client with named group + namespace + resourceResource (with name)
   435  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   436  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   437  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces"),
   438  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces",
   439  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D",
   440  		},
   441  		{
   442  			// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
   443  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
   444  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   445  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   446  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   447  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/finalize",
   448  		},
   449  		{
   450  			// dynamic client with named group + namespace + resourceResource (with NO name) + subresource
   451  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE
   452  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   453  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status"),
   454  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status",
   455  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/status",
   456  		},
   457  		{
   458  			// dynamic client with named group + namespace + resourceResource (with no name)
   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/namespaces"),
   462  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces",
   463  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces",
   464  		},
   465  		{
   466  			// dynamic client with named group + resourceResource (with name) + subresource
   467  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   468  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   469  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/finalize"),
   470  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/finalize",
   471  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/finalize",
   472  		},
   473  		{
   474  			// dynamic client with named group + resourceResource (with name) + subresource
   475  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   476  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   477  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces/status"),
   478  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/status",
   479  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/status",
   480  		},
   481  		{
   482  			// dynamic client with named group + resourceResource (with name)
   483  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   484  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   485  				Prefix("/apis/namespaces/namespaces/namespaces/namespaces"),
   486  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces",
   487  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D",
   488  		},
   489  		{
   490  			// dynamic client with named group + resourceResource (with no name)
   491  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME
   492  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   493  				Prefix("/apis/namespaces/namespaces/namespaces"),
   494  			ExpectedFullURL:  "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
   495  			ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces",
   496  		},
   497  		{
   498  			// dynamic client with wrong api group + namespace + resourceResource (with name) + subresource
   499  			// /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE
   500  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   501  				Prefix("/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"),
   502  			ExpectedFullURL:  "http://localhost/some/base/url/path/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize",
   503  			ExpectedFinalURL: "http://localhost/%7Bprefix%7D",
   504  		},
   505  		{
   506  			// dynamic client with core group + namespace + resourceResource (with name) where baseURL is a single /
   507  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   508  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   509  				Prefix("/api/v1/namespaces/ns/r2/name1"),
   510  			ExpectedFullURL:  "http://localhost/api/v1/namespaces/ns/r2/name1",
   511  			ExpectedFinalURL: "http://localhost/api/v1/namespaces/%7Bnamespace%7D/r2/%7Bname%7D",
   512  		},
   513  		{
   514  			// dynamic client with core group + namespace + resourceResource (with name) where baseURL is 'some/base/url/path'
   515  			// /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME
   516  			Request: NewRequestWithClient(uri, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   517  				Prefix("/api/v1/namespaces/ns/r3/name1"),
   518  			ExpectedFullURL:  "http://localhost/some/base/url/path/api/v1/namespaces/ns/r3/name1",
   519  			ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r3/%7Bname%7D",
   520  		},
   521  		{
   522  			// dynamic client where baseURL is a single /
   523  			// /
   524  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   525  				Prefix("/"),
   526  			ExpectedFullURL:  "http://localhost/",
   527  			ExpectedFinalURL: "http://localhost/",
   528  		},
   529  		{
   530  			// dynamic client where baseURL is a single /
   531  			// /version
   532  			Request: NewRequestWithClient(uriSingleSlash, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("DELETE").
   533  				Prefix("/version"),
   534  			ExpectedFullURL:  "http://localhost/version",
   535  			ExpectedFinalURL: "http://localhost/version",
   536  		},
   537  	}
   538  	for i, testCase := range testCases {
   539  		r := testCase.Request
   540  		full := r.URL()
   541  		if full.String() != testCase.ExpectedFullURL {
   542  			t.Errorf("%d: unexpected initial URL: %s %s", i, full, testCase.ExpectedFullURL)
   543  		}
   544  		actualURL := r.finalURLTemplate()
   545  		actual := actualURL.String()
   546  		if actual != testCase.ExpectedFinalURL {
   547  			t.Errorf("%d: unexpected URL template: %s %s", i, actual, testCase.ExpectedFinalURL)
   548  		}
   549  		if r.URL().String() != full.String() {
   550  			t.Errorf("%d, creating URL template changed request: %s -> %s", i, full.String(), r.URL().String())
   551  		}
   552  	}
   553  }
   554  
   555  func TestTransformResponse(t *testing.T) {
   556  	invalid := []byte("aaaaa")
   557  	uri, _ := url.Parse("http://localhost")
   558  	testCases := []struct {
   559  		Response *http.Response
   560  		Data     []byte
   561  		Created  bool
   562  		Error    bool
   563  		ErrFn    func(err error) bool
   564  	}{
   565  		{Response: &http.Response{StatusCode: http.StatusOK}, Data: []byte{}},
   566  		{Response: &http.Response{StatusCode: http.StatusCreated}, Data: []byte{}, Created: true},
   567  		{Response: &http.Response{StatusCode: 199}, Error: true},
   568  		{Response: &http.Response{StatusCode: http.StatusInternalServerError}, Error: true},
   569  		{Response: &http.Response{StatusCode: http.StatusUnprocessableEntity}, Error: true},
   570  		{Response: &http.Response{StatusCode: http.StatusConflict}, Error: true},
   571  		{Response: &http.Response{StatusCode: http.StatusNotFound}, Error: true},
   572  		{Response: &http.Response{StatusCode: http.StatusUnauthorized}, Error: true},
   573  		{
   574  			Response: &http.Response{
   575  				StatusCode: http.StatusUnauthorized,
   576  				Header:     http.Header{"Content-Type": []string{"application/json"}},
   577  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   578  			},
   579  			Error: true,
   580  			ErrFn: func(err error) bool {
   581  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   582  			},
   583  		},
   584  		{
   585  			Response: &http.Response{
   586  				StatusCode: http.StatusUnauthorized,
   587  				Header:     http.Header{"Content-Type": []string{"text/any"}},
   588  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   589  			},
   590  			Error: true,
   591  			ErrFn: func(err error) bool {
   592  				return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err)
   593  			},
   594  		},
   595  		{Response: &http.Response{StatusCode: http.StatusForbidden}, Error: true},
   596  		{Response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
   597  		{Response: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
   598  	}
   599  	for i, test := range testCases {
   600  		r := NewRequestWithClient(uri, "", defaultContentConfig(), nil)
   601  		if test.Response.Body == nil {
   602  			test.Response.Body = io.NopCloser(bytes.NewReader([]byte{}))
   603  		}
   604  		result := r.transformResponse(test.Response, &http.Request{})
   605  		response, created, err := result.body, result.statusCode == http.StatusCreated, result.err
   606  		hasErr := err != nil
   607  		if hasErr != test.Error {
   608  			t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)
   609  		} else if hasErr && test.Response.StatusCode > 399 {
   610  			status, ok := err.(apierrors.APIStatus)
   611  			if !ok {
   612  				t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err)
   613  				continue
   614  			}
   615  			if int(status.Status().Code) != test.Response.StatusCode {
   616  				t.Errorf("%d: status code did not match response: %#v", i, status.Status())
   617  			}
   618  		}
   619  		if test.ErrFn != nil && !test.ErrFn(err) {
   620  			t.Errorf("%d: error function did not match: %v", i, err)
   621  		}
   622  		if !(test.Data == nil && response == nil) && !apiequality.Semantic.DeepDerivative(test.Data, response) {
   623  			t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response)
   624  		}
   625  		if test.Created != created {
   626  			t.Errorf("%d: expected created %t, got %t", i, test.Created, created)
   627  		}
   628  	}
   629  }
   630  
   631  type renegotiator struct {
   632  	called      bool
   633  	contentType string
   634  	params      map[string]string
   635  	decoder     runtime.Decoder
   636  	err         error
   637  }
   638  
   639  func (r *renegotiator) Decoder(contentType string, params map[string]string) (runtime.Decoder, error) {
   640  	r.called = true
   641  	r.contentType = contentType
   642  	r.params = params
   643  	return r.decoder, r.err
   644  }
   645  
   646  func (r *renegotiator) Encoder(contentType string, params map[string]string) (runtime.Encoder, error) {
   647  	return nil, fmt.Errorf("UNIMPLEMENTED")
   648  }
   649  
   650  func (r *renegotiator) StreamDecoder(contentType string, params map[string]string) (runtime.Decoder, runtime.Serializer, runtime.Framer, error) {
   651  	return nil, nil, nil, fmt.Errorf("UNIMPLEMENTED")
   652  }
   653  
   654  func TestTransformResponseNegotiate(t *testing.T) {
   655  	invalid := []byte("aaaaa")
   656  	uri, _ := url.Parse("http://localhost")
   657  	testCases := []struct {
   658  		Response *http.Response
   659  		Data     []byte
   660  		Created  bool
   661  		Error    bool
   662  		ErrFn    func(err error) bool
   663  
   664  		ContentType       string
   665  		Called            bool
   666  		ExpectContentType string
   667  		Decoder           runtime.Decoder
   668  		NegotiateErr      error
   669  	}{
   670  		{
   671  			ContentType: "application/json",
   672  			Response: &http.Response{
   673  				StatusCode: http.StatusUnauthorized,
   674  				Header:     http.Header{"Content-Type": []string{"application/json"}},
   675  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   676  			},
   677  			Called:            true,
   678  			ExpectContentType: "application/json",
   679  			Error:             true,
   680  			ErrFn: func(err error) bool {
   681  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   682  			},
   683  		},
   684  		{
   685  			ContentType: "application/json",
   686  			Response: &http.Response{
   687  				StatusCode: http.StatusUnauthorized,
   688  				Header:     http.Header{"Content-Type": []string{"application/protobuf"}},
   689  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   690  			},
   691  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   692  
   693  			Called:            true,
   694  			ExpectContentType: "application/protobuf",
   695  
   696  			Error: true,
   697  			ErrFn: func(err error) bool {
   698  				return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err)
   699  			},
   700  		},
   701  		{
   702  			ContentType: "application/json",
   703  			Response: &http.Response{
   704  				StatusCode: http.StatusInternalServerError,
   705  				Header:     http.Header{"Content-Type": []string{"application/,others"}},
   706  			},
   707  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   708  
   709  			Error: true,
   710  			ErrFn: func(err error) bool {
   711  				return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500
   712  			},
   713  		},
   714  		{
   715  			// negotiate when no content type specified
   716  			Response: &http.Response{
   717  				StatusCode: http.StatusOK,
   718  				Header:     http.Header{"Content-Type": []string{"text/any"}},
   719  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   720  			},
   721  			Decoder:           scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   722  			Called:            true,
   723  			ExpectContentType: "text/any",
   724  		},
   725  		{
   726  			// negotiate when no response content type specified
   727  			ContentType: "text/any",
   728  			Response: &http.Response{
   729  				StatusCode: http.StatusOK,
   730  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   731  			},
   732  			Decoder:           scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   733  			Called:            true,
   734  			ExpectContentType: "text/any",
   735  		},
   736  		{
   737  			// unrecognized content type is not handled
   738  			ContentType: "application/json",
   739  			Response: &http.Response{
   740  				StatusCode: http.StatusNotFound,
   741  				Header:     http.Header{"Content-Type": []string{"application/unrecognized"}},
   742  				Body:       io.NopCloser(bytes.NewReader(invalid)),
   743  			},
   744  			Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion),
   745  
   746  			NegotiateErr:      fmt.Errorf("aaaa"),
   747  			Called:            true,
   748  			ExpectContentType: "application/unrecognized",
   749  
   750  			Error: true,
   751  			ErrFn: func(err error) bool {
   752  				return err.Error() != "aaaaa" && apierrors.IsNotFound(err)
   753  			},
   754  		},
   755  	}
   756  	for i, test := range testCases {
   757  		contentConfig := defaultContentConfig()
   758  		contentConfig.ContentType = test.ContentType
   759  		negotiator := &renegotiator{
   760  			decoder: test.Decoder,
   761  			err:     test.NegotiateErr,
   762  		}
   763  		contentConfig.Negotiator = negotiator
   764  		r := NewRequestWithClient(uri, "", contentConfig, nil)
   765  		if test.Response.Body == nil {
   766  			test.Response.Body = io.NopCloser(bytes.NewReader([]byte{}))
   767  		}
   768  		result := r.transformResponse(test.Response, &http.Request{})
   769  		_, err := result.body, result.err
   770  		hasErr := err != nil
   771  		if hasErr != test.Error {
   772  			t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)
   773  			continue
   774  		} else if hasErr && test.Response.StatusCode > 399 {
   775  			status, ok := err.(apierrors.APIStatus)
   776  			if !ok {
   777  				t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err)
   778  				continue
   779  			}
   780  			if int(status.Status().Code) != test.Response.StatusCode {
   781  				t.Errorf("%d: status code did not match response: %#v", i, status.Status())
   782  			}
   783  		}
   784  		if test.ErrFn != nil && !test.ErrFn(err) {
   785  			t.Errorf("%d: error function did not match: %v", i, err)
   786  		}
   787  		if negotiator.called != test.Called {
   788  			t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called)
   789  		}
   790  		if !test.Called {
   791  			continue
   792  		}
   793  		if negotiator.contentType != test.ExpectContentType {
   794  			t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType)
   795  		}
   796  	}
   797  }
   798  
   799  func TestTransformUnstructuredError(t *testing.T) {
   800  	testCases := []struct {
   801  		Req *http.Request
   802  		Res *http.Response
   803  
   804  		Resource string
   805  		Name     string
   806  
   807  		ErrFn       func(error) bool
   808  		Transformed error
   809  	}{
   810  		{
   811  			Resource: "foo",
   812  			Name:     "bar",
   813  			Req: &http.Request{
   814  				Method: "POST",
   815  			},
   816  			Res: &http.Response{
   817  				StatusCode: http.StatusConflict,
   818  				Body:       io.NopCloser(bytes.NewReader(nil)),
   819  			},
   820  			ErrFn: apierrors.IsAlreadyExists,
   821  		},
   822  		{
   823  			Resource: "foo",
   824  			Name:     "bar",
   825  			Req: &http.Request{
   826  				Method: "PUT",
   827  			},
   828  			Res: &http.Response{
   829  				StatusCode: http.StatusConflict,
   830  				Body:       io.NopCloser(bytes.NewReader(nil)),
   831  			},
   832  			ErrFn: apierrors.IsConflict,
   833  		},
   834  		{
   835  			Resource: "foo",
   836  			Name:     "bar",
   837  			Req:      &http.Request{},
   838  			Res: &http.Response{
   839  				StatusCode: http.StatusNotFound,
   840  				Body:       io.NopCloser(bytes.NewReader(nil)),
   841  			},
   842  			ErrFn: apierrors.IsNotFound,
   843  		},
   844  		{
   845  			Req: &http.Request{},
   846  			Res: &http.Response{
   847  				StatusCode: http.StatusBadRequest,
   848  				Body:       io.NopCloser(bytes.NewReader(nil)),
   849  			},
   850  			ErrFn: apierrors.IsBadRequest,
   851  		},
   852  		{
   853  			// status in response overrides transformed result
   854  			Req:   &http.Request{},
   855  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))},
   856  			ErrFn: apierrors.IsBadRequest,
   857  			Transformed: &apierrors.StatusError{
   858  				ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
   859  			},
   860  		},
   861  		{
   862  			// successful status is ignored
   863  			Req:   &http.Request{},
   864  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))},
   865  			ErrFn: apierrors.IsBadRequest,
   866  		},
   867  		{
   868  			// empty object does not change result
   869  			Req:   &http.Request{},
   870  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{}`)))},
   871  			ErrFn: apierrors.IsBadRequest,
   872  		},
   873  		{
   874  			// we default apiVersion for backwards compatibility with old clients
   875  			// TODO: potentially remove in 1.7
   876  			Req:   &http.Request{},
   877  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))},
   878  			ErrFn: apierrors.IsBadRequest,
   879  			Transformed: &apierrors.StatusError{
   880  				ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound},
   881  			},
   882  		},
   883  		{
   884  			// we do not default kind
   885  			Req:   &http.Request{},
   886  			Res:   &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))},
   887  			ErrFn: apierrors.IsBadRequest,
   888  		},
   889  	}
   890  
   891  	for _, testCase := range testCases {
   892  		t.Run("", func(t *testing.T) {
   893  			r := &Request{
   894  				c: &RESTClient{
   895  					content: defaultContentConfig(),
   896  				},
   897  				resourceName: testCase.Name,
   898  				resource:     testCase.Resource,
   899  			}
   900  			result := r.transformResponse(testCase.Res, testCase.Req)
   901  			err := result.err
   902  			if !testCase.ErrFn(err) {
   903  				t.Fatalf("unexpected error: %v", err)
   904  			}
   905  			if !apierrors.IsUnexpectedServerError(err) {
   906  				t.Errorf("unexpected error type: %v", err)
   907  			}
   908  			if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) {
   909  				t.Errorf("unexpected error string: %s", err)
   910  			}
   911  			if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) {
   912  				t.Errorf("unexpected error string: %s", err)
   913  			}
   914  
   915  			// verify Error() properly transforms the error
   916  			transformed := result.Error()
   917  			expect := testCase.Transformed
   918  			if expect == nil {
   919  				expect = err
   920  			}
   921  			if !reflect.DeepEqual(expect, transformed) {
   922  				t.Errorf("unexpected Error(): %s", cmp.Diff(expect, transformed))
   923  			}
   924  
   925  			// verify result.Get properly transforms the error
   926  			if _, err := result.Get(); !reflect.DeepEqual(expect, err) {
   927  				t.Errorf("unexpected error on Get(): %s", cmp.Diff(expect, err))
   928  			}
   929  
   930  			// verify result.Into properly handles the error
   931  			if err := result.Into(&v1.Pod{}); !reflect.DeepEqual(expect, err) {
   932  				t.Errorf("unexpected error on Into(): %s", cmp.Diff(expect, err))
   933  			}
   934  
   935  			// verify result.Raw leaves the error in the untransformed state
   936  			if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) {
   937  				t.Errorf("unexpected error on Raw(): %s", cmp.Diff(expect, err))
   938  			}
   939  		})
   940  	}
   941  }
   942  
   943  func TestRequestWatch(t *testing.T) {
   944  	testCases := []struct {
   945  		name             string
   946  		Request          *Request
   947  		maxRetries       int
   948  		serverReturns    []responseErr
   949  		Expect           []watch.Event
   950  		attemptsExpected int
   951  		Err              bool
   952  		ErrFn            func(error) bool
   953  		Empty            bool
   954  	}{
   955  		{
   956  			name:             "Request has error",
   957  			Request:          &Request{err: errors.New("bail")},
   958  			attemptsExpected: 0,
   959  			Err:              true,
   960  		},
   961  		{
   962  			name:    "Client is nil, should use http.DefaultClient",
   963  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
   964  			Err:     true,
   965  		},
   966  		{
   967  			name: "error is not retryable",
   968  			Request: &Request{
   969  				c: &RESTClient{
   970  					base: &url.URL{},
   971  				},
   972  			},
   973  			serverReturns: []responseErr{
   974  				{response: nil, err: errors.New("err")},
   975  			},
   976  			attemptsExpected: 1,
   977  			Err:              true,
   978  		},
   979  		{
   980  			name: "server returns forbidden",
   981  			Request: &Request{
   982  				c: &RESTClient{
   983  					content: defaultContentConfig(),
   984  					base:    &url.URL{},
   985  				},
   986  			},
   987  			serverReturns: []responseErr{
   988  				{response: &http.Response{
   989  					StatusCode: http.StatusForbidden,
   990  					Body:       io.NopCloser(bytes.NewReader([]byte{})),
   991  				}, err: nil},
   992  			},
   993  			attemptsExpected: 1,
   994  			Expect: []watch.Event{
   995  				{
   996  					Type: watch.Error,
   997  					Object: &metav1.Status{
   998  						Status:  "Failure",
   999  						Code:    500,
  1000  						Reason:  "InternalError",
  1001  						Message: `an error on the server ("unable to decode an event from the watch stream: test error") has prevented the request from succeeding`,
  1002  						Details: &metav1.StatusDetails{
  1003  							Causes: []metav1.StatusCause{
  1004  								{
  1005  									Type:    "UnexpectedServerResponse",
  1006  									Message: "unable to decode an event from the watch stream: test error",
  1007  								},
  1008  								{
  1009  									Type:    "ClientWatchDecoding",
  1010  									Message: "unable to decode an event from the watch stream: test error",
  1011  								},
  1012  							},
  1013  						},
  1014  					},
  1015  				},
  1016  			},
  1017  			Err: true,
  1018  			ErrFn: func(err error) bool {
  1019  				return apierrors.IsForbidden(err)
  1020  			},
  1021  		},
  1022  		{
  1023  			name: "server returns forbidden",
  1024  			Request: &Request{
  1025  				c: &RESTClient{
  1026  					content: defaultContentConfig(),
  1027  					base:    &url.URL{},
  1028  				},
  1029  			},
  1030  			serverReturns: []responseErr{
  1031  				{response: &http.Response{
  1032  					StatusCode: http.StatusForbidden,
  1033  					Body:       io.NopCloser(bytes.NewReader([]byte{})),
  1034  				}, err: nil},
  1035  			},
  1036  			attemptsExpected: 1,
  1037  			Err:              true,
  1038  			ErrFn: func(err error) bool {
  1039  				return apierrors.IsForbidden(err)
  1040  			},
  1041  		},
  1042  		{
  1043  			name: "server returns unauthorized",
  1044  			Request: &Request{
  1045  				c: &RESTClient{
  1046  					content: defaultContentConfig(),
  1047  					base:    &url.URL{},
  1048  				},
  1049  			},
  1050  			serverReturns: []responseErr{
  1051  				{response: &http.Response{
  1052  					StatusCode: http.StatusUnauthorized,
  1053  					Body:       io.NopCloser(bytes.NewReader([]byte{})),
  1054  				}, err: nil},
  1055  			},
  1056  			attemptsExpected: 1,
  1057  			Err:              true,
  1058  			ErrFn: func(err error) bool {
  1059  				return apierrors.IsUnauthorized(err)
  1060  			},
  1061  		},
  1062  		{
  1063  			name: "server returns unauthorized",
  1064  			Request: &Request{
  1065  				c: &RESTClient{
  1066  					content: defaultContentConfig(),
  1067  					base:    &url.URL{},
  1068  				},
  1069  			},
  1070  			serverReturns: []responseErr{
  1071  				{response: &http.Response{
  1072  					StatusCode: http.StatusUnauthorized,
  1073  					Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
  1074  						Status: metav1.StatusFailure,
  1075  						Reason: metav1.StatusReasonUnauthorized,
  1076  					})))),
  1077  				}, err: nil},
  1078  			},
  1079  			attemptsExpected: 1,
  1080  			Err:              true,
  1081  			ErrFn: func(err error) bool {
  1082  				return apierrors.IsUnauthorized(err)
  1083  			},
  1084  		},
  1085  		{
  1086  			name: "server returns EOF error",
  1087  			Request: &Request{
  1088  				c: &RESTClient{
  1089  					base: &url.URL{},
  1090  				},
  1091  			},
  1092  			serverReturns: []responseErr{
  1093  				{response: nil, err: io.EOF},
  1094  			},
  1095  			attemptsExpected: 1,
  1096  			Empty:            true,
  1097  		},
  1098  		{
  1099  			name: "server returns can't write HTTP request on broken connection error",
  1100  			Request: &Request{
  1101  				c: &RESTClient{
  1102  					base: &url.URL{},
  1103  				},
  1104  			},
  1105  			serverReturns: []responseErr{
  1106  				{response: nil, err: errors.New("http: can't write HTTP request on broken connection")},
  1107  			},
  1108  			attemptsExpected: 1,
  1109  			Empty:            true,
  1110  		},
  1111  		{
  1112  			name: "server returns connection reset by peer",
  1113  			Request: &Request{
  1114  				c: &RESTClient{
  1115  					base: &url.URL{},
  1116  				},
  1117  			},
  1118  			serverReturns: []responseErr{
  1119  				{response: nil, err: errors.New("foo: connection reset by peer")},
  1120  			},
  1121  			attemptsExpected: 1,
  1122  			Empty:            true,
  1123  		},
  1124  		{
  1125  			name: "max retries 2, server always returns EOF error",
  1126  			Request: &Request{
  1127  				c: &RESTClient{
  1128  					base: &url.URL{},
  1129  				},
  1130  			},
  1131  			maxRetries:       2,
  1132  			attemptsExpected: 3,
  1133  			serverReturns: []responseErr{
  1134  				{response: nil, err: io.EOF},
  1135  				{response: nil, err: io.EOF},
  1136  				{response: nil, err: io.EOF},
  1137  			},
  1138  			Empty: true,
  1139  		},
  1140  		{
  1141  			name: "max retries 2, server always returns a response with Retry-After header",
  1142  			Request: &Request{
  1143  				c: &RESTClient{
  1144  					base: &url.URL{},
  1145  				},
  1146  			},
  1147  			maxRetries:       2,
  1148  			attemptsExpected: 3,
  1149  			serverReturns: []responseErr{
  1150  				{response: retryAfterResponse(), err: nil},
  1151  				{response: retryAfterResponse(), err: nil},
  1152  				{response: retryAfterResponse(), err: nil},
  1153  			},
  1154  			Err: true,
  1155  			ErrFn: func(err error) bool {
  1156  				return apierrors.IsInternalError(err)
  1157  			},
  1158  		},
  1159  	}
  1160  
  1161  	for _, testCase := range testCases {
  1162  		t.Run(testCase.name, func(t *testing.T) {
  1163  			var attemptsGot int
  1164  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  1165  				defer func() {
  1166  					attemptsGot++
  1167  				}()
  1168  
  1169  				if attemptsGot >= len(testCase.serverReturns) {
  1170  					t.Fatalf("Wrong test setup, the server does not know what to return")
  1171  				}
  1172  				re := testCase.serverReturns[attemptsGot]
  1173  				return re.response, re.err
  1174  			})
  1175  			if c := testCase.Request.c; c != nil && len(testCase.serverReturns) > 0 {
  1176  				c.Client = client
  1177  			}
  1178  			testCase.Request.backoff = &noSleepBackOff{}
  1179  			testCase.Request.maxRetries = testCase.maxRetries
  1180  			testCase.Request.retryFn = defaultRequestRetryFn
  1181  
  1182  			watch, err := testCase.Request.Watch(context.Background())
  1183  
  1184  			if watch == nil && err == nil {
  1185  				t.Fatal("Both watch.Interface and err returned by Watch are nil")
  1186  			}
  1187  			if testCase.attemptsExpected != attemptsGot {
  1188  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", testCase.attemptsExpected, attemptsGot)
  1189  			}
  1190  			hasErr := err != nil
  1191  			if hasErr != testCase.Err {
  1192  				t.Fatalf("expected %t, got %t: %v", testCase.Err, hasErr, err)
  1193  			}
  1194  			if testCase.ErrFn != nil && !testCase.ErrFn(err) {
  1195  				t.Errorf("error not valid: %v", err)
  1196  			}
  1197  			if hasErr && watch != nil {
  1198  				t.Fatalf("watch should be nil when error is returned")
  1199  			}
  1200  			if hasErr {
  1201  				return
  1202  			}
  1203  			defer watch.Stop()
  1204  			if testCase.Empty {
  1205  				evt, ok := <-watch.ResultChan()
  1206  				if ok {
  1207  					t.Errorf("expected the watch to be empty: %#v", evt)
  1208  				}
  1209  			}
  1210  			if testCase.Expect != nil {
  1211  				for i, evt := range testCase.Expect {
  1212  					out, ok := <-watch.ResultChan()
  1213  					if !ok {
  1214  						t.Fatalf("Watch closed early, %d/%d read", i, len(testCase.Expect))
  1215  					}
  1216  					if !reflect.DeepEqual(evt, out) {
  1217  						t.Fatalf("Event %d does not match: %s", i, cmp.Diff(evt, out))
  1218  					}
  1219  				}
  1220  			}
  1221  		})
  1222  	}
  1223  }
  1224  
  1225  func TestRequestStream(t *testing.T) {
  1226  	testCases := []struct {
  1227  		name             string
  1228  		Request          *Request
  1229  		maxRetries       int
  1230  		serverReturns    []responseErr
  1231  		attemptsExpected int
  1232  		Err              bool
  1233  		ErrFn            func(error) bool
  1234  	}{
  1235  		{
  1236  			name:             "request has error",
  1237  			Request:          &Request{err: errors.New("bail")},
  1238  			attemptsExpected: 0,
  1239  			Err:              true,
  1240  		},
  1241  		{
  1242  			name:    "Client is nil, should use http.DefaultClient",
  1243  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
  1244  			Err:     true,
  1245  		},
  1246  		{
  1247  			name: "server returns an error",
  1248  			Request: &Request{
  1249  				c: &RESTClient{
  1250  					base: &url.URL{},
  1251  				},
  1252  			},
  1253  			serverReturns: []responseErr{
  1254  				{response: nil, err: errors.New("err")},
  1255  			},
  1256  			attemptsExpected: 1,
  1257  			Err:              true,
  1258  		},
  1259  		{
  1260  			Request: &Request{
  1261  				c: &RESTClient{
  1262  					content: defaultContentConfig(),
  1263  					base:    &url.URL{},
  1264  				},
  1265  			},
  1266  			serverReturns: []responseErr{
  1267  				{response: &http.Response{
  1268  					StatusCode: http.StatusUnauthorized,
  1269  					Body: io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{
  1270  						Status: metav1.StatusFailure,
  1271  						Reason: metav1.StatusReasonUnauthorized,
  1272  					})))),
  1273  				}, err: nil},
  1274  			},
  1275  			attemptsExpected: 1,
  1276  			Err:              true,
  1277  		},
  1278  		{
  1279  			Request: &Request{
  1280  				c: &RESTClient{
  1281  					content: defaultContentConfig(),
  1282  					base:    &url.URL{},
  1283  				},
  1284  			},
  1285  			serverReturns: []responseErr{
  1286  				{response: &http.Response{
  1287  					StatusCode: http.StatusBadRequest,
  1288  					Body:       io.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}`))),
  1289  				}, err: nil},
  1290  			},
  1291  			attemptsExpected: 1,
  1292  			Err:              true,
  1293  			ErrFn: func(err error) bool {
  1294  				if err.Error() == "a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]" {
  1295  					return true
  1296  				}
  1297  				return false
  1298  			},
  1299  		},
  1300  		{
  1301  			name: "max retries 1, server returns a retry-after response, non-bytes request, no retry",
  1302  			Request: &Request{
  1303  				body: &readSeeker{err: io.EOF},
  1304  				c: &RESTClient{
  1305  					base: &url.URL{},
  1306  				},
  1307  			},
  1308  			maxRetries:       1,
  1309  			attemptsExpected: 1,
  1310  			serverReturns: []responseErr{
  1311  				{response: retryAfterResponse(), err: nil},
  1312  			},
  1313  			Err: true,
  1314  		},
  1315  		{
  1316  			name: "max retries 2, server always returns a response with Retry-After header",
  1317  			Request: &Request{
  1318  				c: &RESTClient{
  1319  					base: &url.URL{},
  1320  				},
  1321  			},
  1322  			maxRetries:       2,
  1323  			attemptsExpected: 3,
  1324  			serverReturns: []responseErr{
  1325  				{response: retryAfterResponse(), err: nil},
  1326  				{response: retryAfterResponse(), err: nil},
  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: "server returns EOF after attempt 1, retry aborted",
  1336  			Request: &Request{
  1337  				c: &RESTClient{
  1338  					base: &url.URL{},
  1339  				},
  1340  			},
  1341  			maxRetries:       2,
  1342  			attemptsExpected: 2,
  1343  			serverReturns: []responseErr{
  1344  				{response: retryAfterResponse(), err: nil},
  1345  				{response: nil, err: io.EOF},
  1346  			},
  1347  			Err: true,
  1348  			ErrFn: func(err error) bool {
  1349  				return unWrap(err) == io.EOF
  1350  			},
  1351  		},
  1352  		{
  1353  			name: "max retries 2, server returns success on the final attempt",
  1354  			Request: &Request{
  1355  				c: &RESTClient{
  1356  					base: &url.URL{},
  1357  				},
  1358  			},
  1359  			maxRetries:       2,
  1360  			attemptsExpected: 3,
  1361  			serverReturns: []responseErr{
  1362  				{response: retryAfterResponse(), err: nil},
  1363  				{response: retryAfterResponse(), err: nil},
  1364  				{response: &http.Response{
  1365  					StatusCode: http.StatusOK,
  1366  					Body:       io.NopCloser(bytes.NewReader([]byte{})),
  1367  				}, err: nil},
  1368  			},
  1369  		},
  1370  	}
  1371  
  1372  	for _, testCase := range testCases {
  1373  		t.Run(testCase.name, func(t *testing.T) {
  1374  			var attemptsGot int
  1375  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  1376  				defer func() {
  1377  					attemptsGot++
  1378  				}()
  1379  
  1380  				if attemptsGot >= len(testCase.serverReturns) {
  1381  					t.Fatalf("Wrong test setup, the server does not know what to return")
  1382  				}
  1383  				re := testCase.serverReturns[attemptsGot]
  1384  				return re.response, re.err
  1385  			})
  1386  			if c := testCase.Request.c; c != nil && len(testCase.serverReturns) > 0 {
  1387  				c.Client = client
  1388  			}
  1389  			testCase.Request.backoff = &noSleepBackOff{}
  1390  			testCase.Request.maxRetries = testCase.maxRetries
  1391  			testCase.Request.retryFn = defaultRequestRetryFn
  1392  
  1393  			body, err := testCase.Request.Stream(context.Background())
  1394  
  1395  			if body == nil && err == nil {
  1396  				t.Fatal("Both body and err returned by Stream are nil")
  1397  			}
  1398  			if testCase.attemptsExpected != attemptsGot {
  1399  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", testCase.attemptsExpected, attemptsGot)
  1400  			}
  1401  
  1402  			hasErr := err != nil
  1403  			if hasErr != testCase.Err {
  1404  				t.Errorf("expected %t, got %t: %v", testCase.Err, hasErr, err)
  1405  			}
  1406  			if hasErr && body != nil {
  1407  				t.Error("body should be nil when error is returned")
  1408  			}
  1409  
  1410  			if hasErr {
  1411  				if testCase.ErrFn != nil && !testCase.ErrFn(err) {
  1412  					t.Errorf("unexpected error: %#v", err)
  1413  				}
  1414  			}
  1415  		})
  1416  	}
  1417  }
  1418  
  1419  func TestRequestDo(t *testing.T) {
  1420  	testCases := []struct {
  1421  		Request *Request
  1422  		Err     bool
  1423  	}{
  1424  		{
  1425  			Request: &Request{c: &RESTClient{}, err: errors.New("bail")},
  1426  			Err:     true,
  1427  		},
  1428  		{
  1429  			Request: &Request{c: &RESTClient{base: &url.URL{}}, pathPrefix: "%"},
  1430  			Err:     true,
  1431  		},
  1432  		{
  1433  			Request: &Request{
  1434  				c: &RESTClient{
  1435  					Client: clientForFunc(func(req *http.Request) (*http.Response, error) {
  1436  						return nil, errors.New("err")
  1437  					}),
  1438  					base: &url.URL{},
  1439  				},
  1440  			},
  1441  			Err: true,
  1442  		},
  1443  	}
  1444  	for i, testCase := range testCases {
  1445  		testCase.Request.backoff = &NoBackoff{}
  1446  		testCase.Request.retryFn = defaultRequestRetryFn
  1447  		body, err := testCase.Request.Do(context.Background()).Raw()
  1448  		hasErr := err != nil
  1449  		if hasErr != testCase.Err {
  1450  			t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err)
  1451  		}
  1452  		if hasErr && body != nil {
  1453  			t.Errorf("%d: body should be nil when error is returned", i)
  1454  		}
  1455  	}
  1456  }
  1457  
  1458  func TestDoRequestNewWay(t *testing.T) {
  1459  	reqBody := "request body"
  1460  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1461  		Protocol:   "TCP",
  1462  		Port:       12345,
  1463  		TargetPort: intstr.FromInt32(12345),
  1464  	}}}}
  1465  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1466  	fakeHandler := utiltesting.FakeHandler{
  1467  		StatusCode:   200,
  1468  		ResponseBody: string(expectedBody),
  1469  		T:            t,
  1470  	}
  1471  	testServer := httptest.NewServer(&fakeHandler)
  1472  	defer testServer.Close()
  1473  	c := testRESTClient(t, testServer)
  1474  	obj, err := c.Verb("POST").
  1475  		Prefix("foo", "bar").
  1476  		Suffix("baz").
  1477  		Timeout(time.Second).
  1478  		Body([]byte(reqBody)).
  1479  		Do(context.Background()).Get()
  1480  	if err != nil {
  1481  		t.Errorf("Unexpected error: %v %#v", err, err)
  1482  		return
  1483  	}
  1484  	if obj == nil {
  1485  		t.Error("nil obj")
  1486  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1487  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1488  	}
  1489  	requestURL := defaultResourcePathWithPrefix("foo/bar", "", "", "baz")
  1490  	requestURL += "?timeout=1s"
  1491  	fakeHandler.ValidateRequest(t, requestURL, "POST", &reqBody)
  1492  }
  1493  
  1494  // This test assumes that the client implementation backs off exponentially, for an individual request.
  1495  func TestBackoffLifecycle(t *testing.T) {
  1496  	count := 0
  1497  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1498  		count++
  1499  		t.Logf("Attempt %d", count)
  1500  		if count == 5 || count == 9 {
  1501  			w.WriteHeader(http.StatusOK)
  1502  			return
  1503  		}
  1504  		w.WriteHeader(http.StatusGatewayTimeout)
  1505  		return
  1506  	}))
  1507  	defer testServer.Close()
  1508  	c := testRESTClient(t, testServer)
  1509  
  1510  	// Test backoff recovery and increase.  This correlates to the constants
  1511  	// which are used in the server implementation returning StatusOK above.
  1512  	seconds := []int{0, 1, 2, 4, 8, 0, 1, 2, 4, 0}
  1513  	request := c.Verb("POST").Prefix("backofftest").Suffix("abc")
  1514  	clock := testingclock.FakeClock{}
  1515  	request.backoff = &URLBackoff{
  1516  		// Use a fake backoff here to avoid flakes and speed the test up.
  1517  		Backoff: flowcontrol.NewFakeBackOff(
  1518  			time.Duration(1)*time.Second,
  1519  			time.Duration(200)*time.Second,
  1520  			&clock,
  1521  		)}
  1522  
  1523  	for _, sec := range seconds {
  1524  		thisBackoff := request.backoff.CalculateBackoff(request.URL())
  1525  		t.Logf("Current backoff %v", thisBackoff)
  1526  		if thisBackoff != time.Duration(sec)*time.Second {
  1527  			t.Errorf("Backoff is %v instead of %v", thisBackoff, sec)
  1528  		}
  1529  		now := clock.Now()
  1530  		request.DoRaw(context.Background())
  1531  		elapsed := clock.Since(now)
  1532  		if clock.Since(now) != thisBackoff {
  1533  			t.Errorf("CalculatedBackoff not honored by clock: Expected time of %v, but got %v ", thisBackoff, elapsed)
  1534  		}
  1535  	}
  1536  }
  1537  
  1538  type testBackoffManager struct {
  1539  	sleeps []time.Duration
  1540  }
  1541  
  1542  func (b *testBackoffManager) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) {
  1543  }
  1544  
  1545  func (b *testBackoffManager) CalculateBackoff(actualUrl *url.URL) time.Duration {
  1546  	return time.Duration(0)
  1547  }
  1548  
  1549  func (b *testBackoffManager) Sleep(d time.Duration) {
  1550  	b.sleeps = append(b.sleeps, d)
  1551  }
  1552  
  1553  func TestCheckRetryClosesBody(t *testing.T) {
  1554  	count := 0
  1555  	ch := make(chan struct{})
  1556  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1557  		count++
  1558  		t.Logf("attempt %d", count)
  1559  		if count >= 5 {
  1560  			w.WriteHeader(http.StatusOK)
  1561  			close(ch)
  1562  			return
  1563  		}
  1564  		w.Header().Set("Retry-After", "1")
  1565  		http.Error(w, "Too many requests, please try again later.", http.StatusTooManyRequests)
  1566  	}))
  1567  	defer testServer.Close()
  1568  
  1569  	backoff := &testBackoffManager{}
  1570  
  1571  	// testBackoffManager.CalculateBackoff always returns 0
  1572  	expectedSleeps := []time.Duration{0, time.Second, time.Second, time.Second, time.Second}
  1573  
  1574  	c := testRESTClient(t, testServer)
  1575  	c.createBackoffMgr = func() BackoffManager { return backoff }
  1576  	_, err := c.Verb("POST").
  1577  		Prefix("foo", "bar").
  1578  		Suffix("baz").
  1579  		Timeout(time.Second).
  1580  		Body([]byte(strings.Repeat("abcd", 1000))).
  1581  		DoRaw(context.Background())
  1582  	if err != nil {
  1583  		t.Fatalf("Unexpected error: %v %#v", err, err)
  1584  	}
  1585  	<-ch
  1586  	if count != 5 {
  1587  		t.Errorf("unexpected retries: %d", count)
  1588  	}
  1589  	if !reflect.DeepEqual(backoff.sleeps, expectedSleeps) {
  1590  		t.Errorf("unexpected sleeps, expected: %v, got: %v", expectedSleeps, backoff.sleeps)
  1591  	}
  1592  }
  1593  
  1594  func TestConnectionResetByPeerIsRetried(t *testing.T) {
  1595  	count := 0
  1596  	backoff := &testBackoffManager{}
  1597  	req := &Request{
  1598  		verb: "GET",
  1599  		c: &RESTClient{
  1600  			Client: clientForFunc(func(req *http.Request) (*http.Response, error) {
  1601  				count++
  1602  				if count >= 3 {
  1603  					return &http.Response{
  1604  						StatusCode: http.StatusOK,
  1605  						Body:       io.NopCloser(bytes.NewReader([]byte{})),
  1606  					}, nil
  1607  				}
  1608  				return nil, &net.OpError{Err: syscall.ECONNRESET}
  1609  			}),
  1610  		},
  1611  		backoff:    backoff,
  1612  		maxRetries: 10,
  1613  		retryFn:    defaultRequestRetryFn,
  1614  	}
  1615  	// We expect two retries of "connection reset by peer" and the success.
  1616  	_, err := req.Do(context.Background()).Raw()
  1617  	if err != nil {
  1618  		t.Errorf("Unexpected error: %v", err)
  1619  	}
  1620  	if count != 3 {
  1621  		t.Errorf("Expected 3 attempts, got: %d", count)
  1622  	}
  1623  	// We have a sleep before each retry (including the initial one) thus 3 together.
  1624  	if len(backoff.sleeps) != 3 {
  1625  		t.Errorf("Expected 3 backoff.Sleep, got: %d", len(backoff.sleeps))
  1626  	}
  1627  }
  1628  
  1629  func TestCheckRetryHandles429And5xx(t *testing.T) {
  1630  	count := 0
  1631  	ch := make(chan struct{})
  1632  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1633  		data, err := io.ReadAll(req.Body)
  1634  		if err != nil {
  1635  			t.Fatalf("unable to read request body: %v", err)
  1636  		}
  1637  		if !bytes.Equal(data, []byte(strings.Repeat("abcd", 1000))) {
  1638  			t.Fatalf("retry did not send a complete body: %s", data)
  1639  		}
  1640  		t.Logf("attempt %d", count)
  1641  		if count >= 4 {
  1642  			w.WriteHeader(http.StatusOK)
  1643  			close(ch)
  1644  			return
  1645  		}
  1646  		w.Header().Set("Retry-After", "0")
  1647  		w.WriteHeader([]int{http.StatusTooManyRequests, 500, 501, 504}[count])
  1648  		count++
  1649  	}))
  1650  	defer testServer.Close()
  1651  
  1652  	c := testRESTClient(t, testServer)
  1653  	_, err := c.Verb("POST").
  1654  		Prefix("foo", "bar").
  1655  		Suffix("baz").
  1656  		Timeout(time.Second).
  1657  		Body([]byte(strings.Repeat("abcd", 1000))).
  1658  		DoRaw(context.Background())
  1659  	if err != nil {
  1660  		t.Fatalf("Unexpected error: %v %#v", err, err)
  1661  	}
  1662  	<-ch
  1663  	if count != 4 {
  1664  		t.Errorf("unexpected retries: %d", count)
  1665  	}
  1666  }
  1667  
  1668  func BenchmarkCheckRetryClosesBody(b *testing.B) {
  1669  	count := 0
  1670  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  1671  		count++
  1672  		if count%3 == 0 {
  1673  			w.WriteHeader(http.StatusOK)
  1674  			return
  1675  		}
  1676  		w.Header().Set("Retry-After", "0")
  1677  		w.WriteHeader(http.StatusTooManyRequests)
  1678  	}))
  1679  	defer testServer.Close()
  1680  
  1681  	c := testRESTClient(b, testServer)
  1682  
  1683  	requests := make([]*Request, 0, b.N)
  1684  	for i := 0; i < b.N; i++ {
  1685  		requests = append(requests, c.Verb("POST").
  1686  			Prefix("foo", "bar").
  1687  			Suffix("baz").
  1688  			Timeout(time.Second).
  1689  			Body([]byte(strings.Repeat("abcd", 1000))))
  1690  	}
  1691  
  1692  	b.ResetTimer()
  1693  	for i := 0; i < b.N; i++ {
  1694  		if _, err := requests[i].DoRaw(context.Background()); err != nil {
  1695  			b.Fatalf("Unexpected error (%d/%d): %v", i, b.N, err)
  1696  		}
  1697  	}
  1698  }
  1699  
  1700  func TestDoRequestNewWayReader(t *testing.T) {
  1701  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1702  	reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1703  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1704  		Protocol:   "TCP",
  1705  		Port:       12345,
  1706  		TargetPort: intstr.FromInt32(12345),
  1707  	}}}}
  1708  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1709  	fakeHandler := utiltesting.FakeHandler{
  1710  		StatusCode:   200,
  1711  		ResponseBody: string(expectedBody),
  1712  		T:            t,
  1713  	}
  1714  	testServer := httptest.NewServer(&fakeHandler)
  1715  	defer testServer.Close()
  1716  	c := testRESTClient(t, testServer)
  1717  	obj, err := c.Verb("POST").
  1718  		Resource("bar").
  1719  		Name("baz").
  1720  		Prefix("foo").
  1721  		Timeout(time.Second).
  1722  		Body(bytes.NewBuffer(reqBodyExpected)).
  1723  		Do(context.Background()).Get()
  1724  	if err != nil {
  1725  		t.Errorf("Unexpected error: %v %#v", err, err)
  1726  		return
  1727  	}
  1728  	if obj == nil {
  1729  		t.Error("nil obj")
  1730  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1731  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1732  	}
  1733  	tmpStr := string(reqBodyExpected)
  1734  	requestURL := defaultResourcePathWithPrefix("foo", "bar", "", "baz")
  1735  	requestURL += "?timeout=1s"
  1736  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1737  }
  1738  
  1739  func TestDoRequestNewWayObj(t *testing.T) {
  1740  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1741  	reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1742  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1743  		Protocol:   "TCP",
  1744  		Port:       12345,
  1745  		TargetPort: intstr.FromInt32(12345),
  1746  	}}}}
  1747  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1748  	fakeHandler := utiltesting.FakeHandler{
  1749  		StatusCode:   200,
  1750  		ResponseBody: string(expectedBody),
  1751  		T:            t,
  1752  	}
  1753  	testServer := httptest.NewServer(&fakeHandler)
  1754  	defer testServer.Close()
  1755  	c := testRESTClient(t, testServer)
  1756  	obj, err := c.Verb("POST").
  1757  		Suffix("baz").
  1758  		Name("bar").
  1759  		Resource("foo").
  1760  		Timeout(time.Second).
  1761  		Body(reqObj).
  1762  		Do(context.Background()).Get()
  1763  	if err != nil {
  1764  		t.Errorf("Unexpected error: %v %#v", err, err)
  1765  		return
  1766  	}
  1767  	if obj == nil {
  1768  		t.Error("nil obj")
  1769  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1770  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1771  	}
  1772  	tmpStr := string(reqBodyExpected)
  1773  	requestURL := defaultResourcePathWithPrefix("", "foo", "", "bar/baz")
  1774  	requestURL += "?timeout=1s"
  1775  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1776  }
  1777  
  1778  func TestDoRequestNewWayFile(t *testing.T) {
  1779  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1780  	reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1781  	if err != nil {
  1782  		t.Errorf("unexpected error: %v", err)
  1783  	}
  1784  
  1785  	file, err := os.CreateTemp("", "foo")
  1786  	if err != nil {
  1787  		t.Errorf("unexpected error: %v", err)
  1788  	}
  1789  	defer file.Close()
  1790  	defer os.Remove(file.Name())
  1791  
  1792  	_, err = file.Write(reqBodyExpected)
  1793  	if err != nil {
  1794  		t.Errorf("unexpected error: %v", err)
  1795  	}
  1796  
  1797  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1798  		Protocol:   "TCP",
  1799  		Port:       12345,
  1800  		TargetPort: intstr.FromInt32(12345),
  1801  	}}}}
  1802  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1803  	fakeHandler := utiltesting.FakeHandler{
  1804  		StatusCode:   200,
  1805  		ResponseBody: string(expectedBody),
  1806  		T:            t,
  1807  	}
  1808  	testServer := httptest.NewServer(&fakeHandler)
  1809  	defer testServer.Close()
  1810  	c := testRESTClient(t, testServer)
  1811  	wasCreated := true
  1812  	obj, err := c.Verb("POST").
  1813  		Prefix("foo/bar", "baz").
  1814  		Timeout(time.Second).
  1815  		Body(file.Name()).
  1816  		Do(context.Background()).WasCreated(&wasCreated).Get()
  1817  	if err != nil {
  1818  		t.Errorf("Unexpected error: %v %#v", err, err)
  1819  		return
  1820  	}
  1821  	if obj == nil {
  1822  		t.Error("nil obj")
  1823  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1824  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1825  	}
  1826  	if wasCreated {
  1827  		t.Errorf("expected object was created")
  1828  	}
  1829  	tmpStr := string(reqBodyExpected)
  1830  	requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "")
  1831  	requestURL += "?timeout=1s"
  1832  	fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr)
  1833  }
  1834  
  1835  func TestWasCreated(t *testing.T) {
  1836  	reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1837  	reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj)
  1838  	if err != nil {
  1839  		t.Errorf("unexpected error: %v", err)
  1840  	}
  1841  
  1842  	expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{
  1843  		Protocol:   "TCP",
  1844  		Port:       12345,
  1845  		TargetPort: intstr.FromInt32(12345),
  1846  	}}}}
  1847  	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj)
  1848  	fakeHandler := utiltesting.FakeHandler{
  1849  		StatusCode:   201,
  1850  		ResponseBody: string(expectedBody),
  1851  		T:            t,
  1852  	}
  1853  	testServer := httptest.NewServer(&fakeHandler)
  1854  	defer testServer.Close()
  1855  	c := testRESTClient(t, testServer)
  1856  	wasCreated := false
  1857  	obj, err := c.Verb("PUT").
  1858  		Prefix("foo/bar", "baz").
  1859  		Timeout(time.Second).
  1860  		Body(reqBodyExpected).
  1861  		Do(context.Background()).WasCreated(&wasCreated).Get()
  1862  	if err != nil {
  1863  		t.Errorf("Unexpected error: %v %#v", err, err)
  1864  		return
  1865  	}
  1866  	if obj == nil {
  1867  		t.Error("nil obj")
  1868  	} else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) {
  1869  		t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
  1870  	}
  1871  	if !wasCreated {
  1872  		t.Errorf("Expected object was created")
  1873  	}
  1874  
  1875  	tmpStr := string(reqBodyExpected)
  1876  	requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "")
  1877  	requestURL += "?timeout=1s"
  1878  	fakeHandler.ValidateRequest(t, requestURL, "PUT", &tmpStr)
  1879  }
  1880  
  1881  func TestVerbs(t *testing.T) {
  1882  	c := testRESTClient(t, nil)
  1883  	if r := c.Post(); r.verb != "POST" {
  1884  		t.Errorf("Post verb is wrong")
  1885  	}
  1886  	if r := c.Put(); r.verb != "PUT" {
  1887  		t.Errorf("Put verb is wrong")
  1888  	}
  1889  	if r := c.Get(); r.verb != "GET" {
  1890  		t.Errorf("Get verb is wrong")
  1891  	}
  1892  	if r := c.Delete(); r.verb != "DELETE" {
  1893  		t.Errorf("Delete verb is wrong")
  1894  	}
  1895  }
  1896  
  1897  func TestAbsPath(t *testing.T) {
  1898  	for i, tc := range []struct {
  1899  		configPrefix   string
  1900  		resourcePrefix string
  1901  		absPath        string
  1902  		wantsAbsPath   string
  1903  	}{
  1904  		{"/", "", "", "/"},
  1905  		{"", "", "/", "/"},
  1906  		{"", "", "/api", "/api"},
  1907  		{"", "", "/api/", "/api/"},
  1908  		{"", "", "/apis", "/apis"},
  1909  		{"", "/foo", "/bar/foo", "/bar/foo"},
  1910  		{"", "/api/foo/123", "/bar/foo", "/bar/foo"},
  1911  		{"/p1", "", "", "/p1"},
  1912  		{"/p1", "", "/", "/p1/"},
  1913  		{"/p1", "", "/api", "/p1/api"},
  1914  		{"/p1", "", "/apis", "/p1/apis"},
  1915  		{"/p1", "/r1", "/apis", "/p1/apis"},
  1916  		{"/p1", "/api/r1", "/apis", "/p1/apis"},
  1917  		{"/p1/api/p2", "", "", "/p1/api/p2"},
  1918  		{"/p1/api/p2", "", "/", "/p1/api/p2/"},
  1919  		{"/p1/api/p2", "", "/api", "/p1/api/p2/api"},
  1920  		{"/p1/api/p2", "", "/api/", "/p1/api/p2/api/"},
  1921  		{"/p1/api/p2", "/r1", "/api/", "/p1/api/p2/api/"},
  1922  		{"/p1/api/p2", "/api/r1", "/api/", "/p1/api/p2/api/"},
  1923  	} {
  1924  		u, _ := url.Parse("http://localhost:123" + tc.configPrefix)
  1925  		r := NewRequestWithClient(u, "", ClientContentConfig{GroupVersion: schema.GroupVersion{Group: "test"}}, nil).Verb("POST").Prefix(tc.resourcePrefix).AbsPath(tc.absPath)
  1926  		if r.pathPrefix != tc.wantsAbsPath {
  1927  			t.Errorf("test case %d failed, unexpected path: %q, expected %q", i, r.pathPrefix, tc.wantsAbsPath)
  1928  		}
  1929  	}
  1930  }
  1931  
  1932  func TestUnacceptableParamNames(t *testing.T) {
  1933  	table := []struct {
  1934  		name          string
  1935  		testVal       string
  1936  		expectSuccess bool
  1937  	}{
  1938  		// timeout is no longer "protected"
  1939  		{"timeout", "42", true},
  1940  	}
  1941  
  1942  	for _, item := range table {
  1943  		c := testRESTClient(t, nil)
  1944  		r := c.Get().setParam(item.name, item.testVal)
  1945  		if e, a := item.expectSuccess, r.err == nil; e != a {
  1946  			t.Errorf("expected %v, got %v (%v)", e, a, r.err)
  1947  		}
  1948  	}
  1949  }
  1950  
  1951  func TestBody(t *testing.T) {
  1952  	const data = "test payload"
  1953  
  1954  	obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
  1955  	bodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), obj)
  1956  
  1957  	f, err := os.CreateTemp("", "test_body")
  1958  	if err != nil {
  1959  		t.Fatalf("TempFile error: %v", err)
  1960  	}
  1961  	if _, err := f.WriteString(data); err != nil {
  1962  		t.Fatalf("TempFile.WriteString error: %v", err)
  1963  	}
  1964  	f.Close()
  1965  	defer os.Remove(f.Name())
  1966  
  1967  	var nilObject *metav1.DeleteOptions
  1968  	typedObject := interface{}(nilObject)
  1969  	c := testRESTClient(t, nil)
  1970  	tests := []struct {
  1971  		input    interface{}
  1972  		expected string
  1973  		headers  map[string]string
  1974  	}{
  1975  		{[]byte(data), data, nil},
  1976  		{f.Name(), data, nil},
  1977  		{strings.NewReader(data), data, nil},
  1978  		{obj, string(bodyExpected), map[string]string{"Content-Type": "application/json"}},
  1979  		{typedObject, "", nil},
  1980  	}
  1981  	for i, tt := range tests {
  1982  		r := c.Post().Body(tt.input)
  1983  		if r.err != nil {
  1984  			t.Errorf("%d: r.Body(%#v) error: %v", i, tt, r.err)
  1985  			continue
  1986  		}
  1987  		if tt.headers != nil {
  1988  			for k, v := range tt.headers {
  1989  				if r.headers.Get(k) != v {
  1990  					t.Errorf("%d: r.headers[%q] = %q; want %q", i, k, v, v)
  1991  				}
  1992  			}
  1993  		}
  1994  
  1995  		req, err := r.newHTTPRequest(context.Background())
  1996  		if err != nil {
  1997  			t.Fatal(err)
  1998  		}
  1999  		if req.Body == nil {
  2000  			if len(tt.expected) != 0 {
  2001  				t.Errorf("%d: req.Body = %q; want %q", i, req.Body, tt.expected)
  2002  			}
  2003  			continue
  2004  		}
  2005  		buf := make([]byte, len(tt.expected))
  2006  		if _, err := req.Body.Read(buf); err != nil {
  2007  			t.Errorf("%d: req.Body.Read error: %v", i, err)
  2008  			continue
  2009  		}
  2010  		body := string(buf)
  2011  		if body != tt.expected {
  2012  			t.Errorf("%d: req.Body = %q; want %q", i, body, tt.expected)
  2013  		}
  2014  	}
  2015  }
  2016  
  2017  func TestWatch(t *testing.T) {
  2018  	tests := []struct {
  2019  		name       string
  2020  		maxRetries int
  2021  	}{
  2022  		{
  2023  			name:       "no retry",
  2024  			maxRetries: 0,
  2025  		},
  2026  		{
  2027  			name:       "with retries",
  2028  			maxRetries: 3,
  2029  		},
  2030  	}
  2031  
  2032  	for _, test := range tests {
  2033  		t.Run(test.name, func(t *testing.T) {
  2034  			var table = []struct {
  2035  				t   watch.EventType
  2036  				obj runtime.Object
  2037  			}{
  2038  				{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2039  				{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2040  				{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2041  			}
  2042  
  2043  			var attempts int
  2044  			testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2045  				defer func() {
  2046  					attempts++
  2047  				}()
  2048  
  2049  				flusher, ok := w.(http.Flusher)
  2050  				if !ok {
  2051  					panic("need flusher!")
  2052  				}
  2053  
  2054  				if attempts < test.maxRetries {
  2055  					w.Header().Set("Retry-After", "1")
  2056  					w.WriteHeader(http.StatusTooManyRequests)
  2057  					return
  2058  				}
  2059  
  2060  				w.Header().Set("Transfer-Encoding", "chunked")
  2061  				w.WriteHeader(http.StatusOK)
  2062  				flusher.Flush()
  2063  
  2064  				encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2065  				for _, item := range table {
  2066  					if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2067  						panic(err)
  2068  					}
  2069  					flusher.Flush()
  2070  				}
  2071  			}))
  2072  			defer testServer.Close()
  2073  
  2074  			s := testRESTClient(t, testServer)
  2075  			watching, err := s.Get().Prefix("path/to/watch/thing").
  2076  				MaxRetries(test.maxRetries).Watch(context.Background())
  2077  			if err != nil {
  2078  				t.Fatalf("Unexpected error: %v", err)
  2079  			}
  2080  
  2081  			for _, item := range table {
  2082  				got, ok := <-watching.ResultChan()
  2083  				if !ok {
  2084  					t.Fatalf("Unexpected early close")
  2085  				}
  2086  				if e, a := item.t, got.Type; e != a {
  2087  					t.Errorf("Expected %v, got %v", e, a)
  2088  				}
  2089  				if e, a := item.obj, got.Object; !apiequality.Semantic.DeepDerivative(e, a) {
  2090  					t.Errorf("Expected %v, got %v", e, a)
  2091  				}
  2092  			}
  2093  
  2094  			_, ok := <-watching.ResultChan()
  2095  			if ok {
  2096  				t.Fatal("Unexpected non-close")
  2097  			}
  2098  		})
  2099  	}
  2100  }
  2101  
  2102  func TestWatchNonDefaultContentType(t *testing.T) {
  2103  	var table = []struct {
  2104  		t   watch.EventType
  2105  		obj runtime.Object
  2106  	}{
  2107  		{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2108  		{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2109  		{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2110  	}
  2111  
  2112  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2113  		flusher, ok := w.(http.Flusher)
  2114  		if !ok {
  2115  			panic("need flusher!")
  2116  		}
  2117  
  2118  		w.Header().Set("Transfer-Encoding", "chunked")
  2119  		// manually set the content type here so we get the renegotiation behavior
  2120  		w.Header().Set("Content-Type", "application/json")
  2121  		w.WriteHeader(http.StatusOK)
  2122  		flusher.Flush()
  2123  
  2124  		encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2125  		for _, item := range table {
  2126  			if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2127  				panic(err)
  2128  			}
  2129  			flusher.Flush()
  2130  		}
  2131  	}))
  2132  	defer testServer.Close()
  2133  
  2134  	// set the default content type to protobuf so that we test falling back to JSON serialization
  2135  	contentConfig := defaultContentConfig()
  2136  	contentConfig.ContentType = "application/vnd.kubernetes.protobuf"
  2137  	s := testRESTClientWithConfig(t, testServer, contentConfig)
  2138  	watching, err := s.Get().Prefix("path/to/watch/thing").Watch(context.Background())
  2139  	if err != nil {
  2140  		t.Fatalf("Unexpected error")
  2141  	}
  2142  
  2143  	for _, item := range table {
  2144  		got, ok := <-watching.ResultChan()
  2145  		if !ok {
  2146  			t.Fatalf("Unexpected early close")
  2147  		}
  2148  		if e, a := item.t, got.Type; e != a {
  2149  			t.Errorf("Expected %v, got %v", e, a)
  2150  		}
  2151  		if e, a := item.obj, got.Object; !apiequality.Semantic.DeepDerivative(e, a) {
  2152  			t.Errorf("Expected %v, got %v", e, a)
  2153  		}
  2154  	}
  2155  
  2156  	_, ok := <-watching.ResultChan()
  2157  	if ok {
  2158  		t.Fatal("Unexpected non-close")
  2159  	}
  2160  }
  2161  
  2162  func TestWatchUnknownContentType(t *testing.T) {
  2163  	var table = []struct {
  2164  		t   watch.EventType
  2165  		obj runtime.Object
  2166  	}{
  2167  		{watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}},
  2168  		{watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}},
  2169  		{watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}},
  2170  	}
  2171  
  2172  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2173  		flusher, ok := w.(http.Flusher)
  2174  		if !ok {
  2175  			panic("need flusher!")
  2176  		}
  2177  
  2178  		w.Header().Set("Transfer-Encoding", "chunked")
  2179  		// manually set the content type here so we get the renegotiation behavior
  2180  		w.Header().Set("Content-Type", "foobar")
  2181  		w.WriteHeader(http.StatusOK)
  2182  		flusher.Flush()
  2183  
  2184  		encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion))
  2185  		for _, item := range table {
  2186  			if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil {
  2187  				panic(err)
  2188  			}
  2189  			flusher.Flush()
  2190  		}
  2191  	}))
  2192  	defer testServer.Close()
  2193  
  2194  	s := testRESTClient(t, testServer)
  2195  	_, err := s.Get().Prefix("path/to/watch/thing").Watch(context.Background())
  2196  	if err == nil {
  2197  		t.Fatalf("Expected to fail due to lack of known stream serialization for content type")
  2198  	}
  2199  }
  2200  
  2201  func TestStream(t *testing.T) {
  2202  	expectedBody := "expected body"
  2203  
  2204  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2205  		flusher, ok := w.(http.Flusher)
  2206  		if !ok {
  2207  			panic("need flusher!")
  2208  		}
  2209  		w.Header().Set("Transfer-Encoding", "chunked")
  2210  		w.WriteHeader(http.StatusOK)
  2211  		w.Write([]byte(expectedBody))
  2212  		flusher.Flush()
  2213  	}))
  2214  	defer testServer.Close()
  2215  
  2216  	s := testRESTClient(t, testServer)
  2217  	readCloser, err := s.Get().Prefix("path/to/stream/thing").Stream(context.Background())
  2218  	if err != nil {
  2219  		t.Fatalf("unexpected error: %v", err)
  2220  	}
  2221  	defer readCloser.Close()
  2222  	buf := new(bytes.Buffer)
  2223  	buf.ReadFrom(readCloser)
  2224  	resultBody := buf.String()
  2225  
  2226  	if expectedBody != resultBody {
  2227  		t.Errorf("Expected %s, got %s", expectedBody, resultBody)
  2228  	}
  2229  }
  2230  
  2231  func testRESTClientWithConfig(t testing.TB, srv *httptest.Server, contentConfig ClientContentConfig) *RESTClient {
  2232  	base, _ := url.Parse("http://localhost")
  2233  	var c *http.Client
  2234  	if srv != nil {
  2235  		var err error
  2236  		base, err = url.Parse(srv.URL)
  2237  		if err != nil {
  2238  			t.Fatalf("failed to parse test URL: %v", err)
  2239  		}
  2240  		c = srv.Client()
  2241  	}
  2242  	versionedAPIPath := defaultResourcePathWithPrefix("", "", "", "")
  2243  	client, err := NewRESTClient(base, versionedAPIPath, contentConfig, nil, c)
  2244  	if err != nil {
  2245  		t.Fatalf("failed to create a client: %v", err)
  2246  	}
  2247  	return client
  2248  
  2249  }
  2250  
  2251  func testRESTClient(t testing.TB, srv *httptest.Server) *RESTClient {
  2252  	contentConfig := defaultContentConfig()
  2253  	return testRESTClientWithConfig(t, srv, contentConfig)
  2254  }
  2255  
  2256  func TestDoContext(t *testing.T) {
  2257  	receivedCh := make(chan struct{})
  2258  	block := make(chan struct{})
  2259  	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  2260  		close(receivedCh)
  2261  		<-block
  2262  		w.WriteHeader(http.StatusOK)
  2263  	}))
  2264  	defer testServer.Close()
  2265  	defer close(block)
  2266  
  2267  	ctx, cancel := context.WithCancel(context.Background())
  2268  	defer cancel()
  2269  
  2270  	go func() {
  2271  		<-receivedCh
  2272  		cancel()
  2273  	}()
  2274  
  2275  	c := testRESTClient(t, testServer)
  2276  	_, err := c.Verb("GET").
  2277  		Prefix("foo").
  2278  		DoRaw(ctx)
  2279  	if err == nil {
  2280  		t.Fatal("Expected context cancellation error")
  2281  	}
  2282  }
  2283  
  2284  func buildString(length int) string {
  2285  	s := make([]byte, length)
  2286  	for i := range s {
  2287  		s[i] = 'a'
  2288  	}
  2289  	return string(s)
  2290  }
  2291  
  2292  func init() {
  2293  	klog.InitFlags(nil)
  2294  }
  2295  
  2296  func TestTruncateBody(t *testing.T) {
  2297  	tests := []struct {
  2298  		body  string
  2299  		want  string
  2300  		level string
  2301  	}{
  2302  		// Anything below 8 is completely truncated
  2303  		{
  2304  			body:  "Completely truncated below 8",
  2305  			want:  " [truncated 28 chars]",
  2306  			level: "0",
  2307  		},
  2308  		// Small strings are not truncated by high levels
  2309  		{
  2310  			body:  "Small body never gets truncated",
  2311  			want:  "Small body never gets truncated",
  2312  			level: "10",
  2313  		},
  2314  		{
  2315  			body:  "Small body never gets truncated",
  2316  			want:  "Small body never gets truncated",
  2317  			level: "8",
  2318  		},
  2319  		// Strings are truncated to 1024 if level is less than 9.
  2320  		{
  2321  			body:  buildString(2000),
  2322  			level: "8",
  2323  			want:  fmt.Sprintf("%s [truncated 976 chars]", buildString(1024)),
  2324  		},
  2325  		// Strings are truncated to 10240 if level is 9.
  2326  		{
  2327  			body:  buildString(20000),
  2328  			level: "9",
  2329  			want:  fmt.Sprintf("%s [truncated 9760 chars]", buildString(10240)),
  2330  		},
  2331  		// Strings are not truncated if level is 10 or higher
  2332  		{
  2333  			body:  buildString(20000),
  2334  			level: "10",
  2335  			want:  buildString(20000),
  2336  		},
  2337  		// Strings are not truncated if level is 10 or higher
  2338  		{
  2339  			body:  buildString(20000),
  2340  			level: "11",
  2341  			want:  buildString(20000),
  2342  		},
  2343  	}
  2344  
  2345  	l := flag.Lookup("v").Value.(flag.Getter).Get().(klog.Level)
  2346  	for _, test := range tests {
  2347  		flag.Set("v", test.level)
  2348  		got := truncateBody(test.body)
  2349  		if got != test.want {
  2350  			t.Errorf("truncateBody(%v) = %v, want %v", test.body, got, test.want)
  2351  		}
  2352  	}
  2353  	flag.Set("v", l.String())
  2354  }
  2355  
  2356  func defaultResourcePathWithPrefix(prefix, resource, namespace, name string) string {
  2357  	var path string
  2358  	path = "/api/" + v1.SchemeGroupVersion.Version
  2359  
  2360  	if prefix != "" {
  2361  		path = path + "/" + prefix
  2362  	}
  2363  	if namespace != "" {
  2364  		path = path + "/namespaces/" + namespace
  2365  	}
  2366  	// Resource names are lower case.
  2367  	resource = strings.ToLower(resource)
  2368  	if resource != "" {
  2369  		path = path + "/" + resource
  2370  	}
  2371  	if name != "" {
  2372  		path = path + "/" + name
  2373  	}
  2374  	return path
  2375  }
  2376  
  2377  func TestRequestPreflightCheck(t *testing.T) {
  2378  	for _, tt := range []struct {
  2379  		name         string
  2380  		verbs        []string
  2381  		namespace    string
  2382  		resourceName string
  2383  		namespaceSet bool
  2384  		expectsErr   bool
  2385  	}{
  2386  		{
  2387  			name:         "no namespace set",
  2388  			verbs:        []string{"GET", "PUT", "DELETE", "POST"},
  2389  			namespaceSet: false,
  2390  			expectsErr:   false,
  2391  		},
  2392  		{
  2393  			name:         "empty resource name and namespace",
  2394  			verbs:        []string{"GET", "PUT", "DELETE"},
  2395  			namespaceSet: true,
  2396  			expectsErr:   false,
  2397  		},
  2398  		{
  2399  			name:         "resource name with empty namespace",
  2400  			verbs:        []string{"GET", "PUT", "DELETE"},
  2401  			namespaceSet: true,
  2402  			resourceName: "ResourceName",
  2403  			expectsErr:   true,
  2404  		},
  2405  		{
  2406  			name:         "post empty resource name and namespace",
  2407  			verbs:        []string{"POST"},
  2408  			namespaceSet: true,
  2409  			expectsErr:   true,
  2410  		},
  2411  		{
  2412  			name:         "working requests",
  2413  			verbs:        []string{"GET", "PUT", "DELETE", "POST"},
  2414  			namespaceSet: true,
  2415  			resourceName: "ResourceName",
  2416  			namespace:    "Namespace",
  2417  			expectsErr:   false,
  2418  		},
  2419  	} {
  2420  		t.Run(tt.name, func(t *testing.T) {
  2421  			for _, verb := range tt.verbs {
  2422  				r := &Request{
  2423  					verb:         verb,
  2424  					namespace:    tt.namespace,
  2425  					resourceName: tt.resourceName,
  2426  					namespaceSet: tt.namespaceSet,
  2427  				}
  2428  
  2429  				err := r.requestPreflightCheck()
  2430  				hasErr := err != nil
  2431  				if hasErr == tt.expectsErr {
  2432  					return
  2433  				}
  2434  				t.Errorf("%s: expects error: %v, has error: %v", verb, tt.expectsErr, hasErr)
  2435  			}
  2436  		})
  2437  	}
  2438  }
  2439  
  2440  func TestThrottledLogger(t *testing.T) {
  2441  	now := time.Now()
  2442  	oldClock := globalThrottledLogger.clock
  2443  	defer func() {
  2444  		globalThrottledLogger.clock = oldClock
  2445  	}()
  2446  	clock := testingclock.NewFakeClock(now)
  2447  	globalThrottledLogger.clock = clock
  2448  
  2449  	logMessages := 0
  2450  	for i := 0; i < 1000; i++ {
  2451  		var wg sync.WaitGroup
  2452  		wg.Add(10)
  2453  		for j := 0; j < 10; j++ {
  2454  			go func() {
  2455  				if _, ok := globalThrottledLogger.attemptToLog(); ok {
  2456  					logMessages++
  2457  				}
  2458  				wg.Done()
  2459  			}()
  2460  		}
  2461  		wg.Wait()
  2462  		now = now.Add(1 * time.Second)
  2463  		clock.SetTime(now)
  2464  	}
  2465  
  2466  	if a, e := logMessages, 100; a != e {
  2467  		t.Fatalf("expected %v log messages, but got %v", e, a)
  2468  	}
  2469  }
  2470  
  2471  func TestRequestMaxRetries(t *testing.T) {
  2472  	successAtNthCalls := 1
  2473  	actualCalls := 0
  2474  	retryOneTimeHandler := func(w http.ResponseWriter, req *http.Request) {
  2475  		defer func() { actualCalls++ }()
  2476  		if actualCalls >= successAtNthCalls {
  2477  			w.WriteHeader(http.StatusOK)
  2478  			return
  2479  		}
  2480  		w.Header().Set("Retry-After", "1")
  2481  		w.WriteHeader(http.StatusTooManyRequests)
  2482  		actualCalls++
  2483  	}
  2484  	testServer := httptest.NewServer(http.HandlerFunc(retryOneTimeHandler))
  2485  	defer testServer.Close()
  2486  
  2487  	u, err := url.Parse(testServer.URL)
  2488  	if err != nil {
  2489  		t.Error(err)
  2490  	}
  2491  
  2492  	testCases := []struct {
  2493  		name        string
  2494  		maxRetries  int
  2495  		expectError bool
  2496  	}{
  2497  		{
  2498  			name:        "no retrying should fail",
  2499  			maxRetries:  0,
  2500  			expectError: true,
  2501  		},
  2502  		{
  2503  			name:        "1 max-retry should exactly work",
  2504  			maxRetries:  1,
  2505  			expectError: false,
  2506  		},
  2507  		{
  2508  			name:        "5 max-retry should work",
  2509  			maxRetries:  5,
  2510  			expectError: false,
  2511  		},
  2512  	}
  2513  
  2514  	for _, testCase := range testCases {
  2515  		t.Run(testCase.name, func(t *testing.T) {
  2516  			defer func() { actualCalls = 0 }()
  2517  			_, err := NewRequestWithClient(u, "", defaultContentConfig(), testServer.Client()).
  2518  				Verb("get").
  2519  				MaxRetries(testCase.maxRetries).
  2520  				AbsPath("/foo").
  2521  				DoRaw(context.TODO())
  2522  			hasError := err != nil
  2523  			if testCase.expectError != hasError {
  2524  				t.Error(" failed checking error")
  2525  			}
  2526  		})
  2527  	}
  2528  }
  2529  
  2530  type responseErr struct {
  2531  	response *http.Response
  2532  	err      error
  2533  }
  2534  
  2535  type seek struct {
  2536  	offset int64
  2537  	whence int
  2538  }
  2539  
  2540  type count struct {
  2541  	// keeps track of the number of Seek(offset, whence) calls.
  2542  	seeks []seek
  2543  
  2544  	// how many times {Request|Response}.Body.Close() has been invoked
  2545  	lock   sync.Mutex
  2546  	closes int
  2547  }
  2548  
  2549  func (c *count) close() {
  2550  	c.lock.Lock()
  2551  	defer c.lock.Unlock()
  2552  	c.closes++
  2553  }
  2554  func (c *count) getCloseCount() int {
  2555  	c.lock.Lock()
  2556  	defer c.lock.Unlock()
  2557  	return c.closes
  2558  }
  2559  
  2560  // used to track {Request|Response}.Body
  2561  type readTracker struct {
  2562  	delegated io.Reader
  2563  	count     *count
  2564  }
  2565  
  2566  func (r *readTracker) Seek(offset int64, whence int) (int64, error) {
  2567  	if seeker, ok := r.delegated.(io.Seeker); ok {
  2568  		r.count.seeks = append(r.count.seeks, seek{offset: offset, whence: whence})
  2569  		return seeker.Seek(offset, whence)
  2570  	}
  2571  	return 0, io.EOF
  2572  }
  2573  
  2574  func (r *readTracker) Read(p []byte) (n int, err error) {
  2575  	return r.delegated.Read(p)
  2576  }
  2577  
  2578  func (r *readTracker) Close() error {
  2579  	if closer, ok := r.delegated.(io.Closer); ok {
  2580  		r.count.close()
  2581  		return closer.Close()
  2582  	}
  2583  	return nil
  2584  }
  2585  
  2586  func newReadTracker(count *count) *readTracker {
  2587  	return &readTracker{
  2588  		count: count,
  2589  	}
  2590  }
  2591  
  2592  func newCount() *count {
  2593  	return &count{
  2594  		closes: 0,
  2595  		seeks:  make([]seek, 0),
  2596  	}
  2597  }
  2598  
  2599  type readSeeker struct{ err error }
  2600  
  2601  func (rs readSeeker) Read([]byte) (int, error)       { return 0, rs.err }
  2602  func (rs readSeeker) Seek(int64, int) (int64, error) { return 0, rs.err }
  2603  
  2604  func unWrap(err error) error {
  2605  	if uerr, ok := err.(*url.Error); ok {
  2606  		return uerr.Err
  2607  	}
  2608  	return err
  2609  }
  2610  
  2611  // noSleepBackOff is a NoBackoff except it does not sleep,
  2612  // used for faster execution of the unit tests.
  2613  type noSleepBackOff struct {
  2614  	*NoBackoff
  2615  }
  2616  
  2617  func (n *noSleepBackOff) Sleep(d time.Duration) {}
  2618  
  2619  func TestRequestWithRetry(t *testing.T) {
  2620  	tests := []struct {
  2621  		name                         string
  2622  		body                         io.Reader
  2623  		bodyBytes                    []byte
  2624  		serverReturns                responseErr
  2625  		errExpected                  error
  2626  		errContains                  string
  2627  		transformFuncInvokedExpected int
  2628  		roundTripInvokedExpected     int
  2629  	}{
  2630  		{
  2631  			name:                         "server returns retry-after response, no request body, retry goes ahead",
  2632  			bodyBytes:                    nil,
  2633  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2634  			errExpected:                  nil,
  2635  			transformFuncInvokedExpected: 1,
  2636  			roundTripInvokedExpected:     2,
  2637  		},
  2638  		{
  2639  			name:                         "server returns retry-after response, bytes request body, retry goes ahead",
  2640  			bodyBytes:                    []byte{},
  2641  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2642  			errExpected:                  nil,
  2643  			transformFuncInvokedExpected: 1,
  2644  			roundTripInvokedExpected:     2,
  2645  		},
  2646  		{
  2647  			name:                         "server returns retry-after response, opaque request body, retry aborted",
  2648  			body:                         &readSeeker{},
  2649  			serverReturns:                responseErr{response: retryAfterResponse(), err: nil},
  2650  			errExpected:                  nil,
  2651  			transformFuncInvokedExpected: 1,
  2652  			roundTripInvokedExpected:     1,
  2653  		},
  2654  		{
  2655  			name:                         "server returns retryable err, no request body, retry goes ahead",
  2656  			bodyBytes:                    nil,
  2657  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2658  			errExpected:                  io.ErrUnexpectedEOF,
  2659  			transformFuncInvokedExpected: 0,
  2660  			roundTripInvokedExpected:     2,
  2661  		},
  2662  		{
  2663  			name:                         "server returns retryable err, bytes request body, retry goes ahead",
  2664  			bodyBytes:                    []byte{},
  2665  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2666  			errExpected:                  io.ErrUnexpectedEOF,
  2667  			transformFuncInvokedExpected: 0,
  2668  			roundTripInvokedExpected:     2,
  2669  		},
  2670  		{
  2671  			name:                         "server returns retryable err, opaque request body, retry aborted",
  2672  			body:                         &readSeeker{},
  2673  			serverReturns:                responseErr{response: nil, err: io.ErrUnexpectedEOF},
  2674  			errExpected:                  io.ErrUnexpectedEOF,
  2675  			transformFuncInvokedExpected: 0,
  2676  			roundTripInvokedExpected:     1,
  2677  		},
  2678  	}
  2679  
  2680  	for _, test := range tests {
  2681  		t.Run(test.name, func(t *testing.T) {
  2682  			var roundTripInvoked int
  2683  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  2684  				roundTripInvoked++
  2685  				return test.serverReturns.response, test.serverReturns.err
  2686  			})
  2687  
  2688  			req := &Request{
  2689  				verb: "GET",
  2690  				body: test.body,
  2691  				c: &RESTClient{
  2692  					Client: client,
  2693  				},
  2694  				backoff:    &noSleepBackOff{},
  2695  				maxRetries: 1,
  2696  				retryFn:    defaultRequestRetryFn,
  2697  			}
  2698  
  2699  			var transformFuncInvoked int
  2700  			err := req.request(context.Background(), func(request *http.Request, response *http.Response) {
  2701  				transformFuncInvoked++
  2702  			})
  2703  
  2704  			if test.roundTripInvokedExpected != roundTripInvoked {
  2705  				t.Errorf("Expected RoundTrip to be invoked %d times, but got: %d", test.roundTripInvokedExpected, roundTripInvoked)
  2706  			}
  2707  			if test.transformFuncInvokedExpected != transformFuncInvoked {
  2708  				t.Errorf("Expected transform func to be invoked %d times, but got: %d", test.transformFuncInvokedExpected, transformFuncInvoked)
  2709  			}
  2710  			switch {
  2711  			case test.errExpected != nil:
  2712  				if test.errExpected != unWrap(err) {
  2713  					t.Errorf("Expected error: %v, but got: %v", test.errExpected, unWrap(err))
  2714  				}
  2715  			case len(test.errContains) > 0:
  2716  				if !strings.Contains(err.Error(), test.errContains) {
  2717  					t.Errorf("Expected error message to caontain: %q, but got: %q", test.errContains, err.Error())
  2718  				}
  2719  			}
  2720  		})
  2721  	}
  2722  }
  2723  
  2724  func TestRequestDoWithRetry(t *testing.T) {
  2725  	testRequestWithRetry(t, "Do", func(ctx context.Context, r *Request) {
  2726  		r.Do(ctx)
  2727  	})
  2728  }
  2729  
  2730  func TestRequestDoRawWithRetry(t *testing.T) {
  2731  	// both request.Do and request.DoRaw have the same behavior and expectations
  2732  	testRequestWithRetry(t, "Do", func(ctx context.Context, r *Request) {
  2733  		r.DoRaw(ctx)
  2734  	})
  2735  }
  2736  
  2737  func TestRequestStreamWithRetry(t *testing.T) {
  2738  	testRequestWithRetry(t, "Stream", func(ctx context.Context, r *Request) {
  2739  		r.Stream(ctx)
  2740  	})
  2741  }
  2742  
  2743  func TestRequestWatchWithRetry(t *testing.T) {
  2744  	testRequestWithRetry(t, "Watch", func(ctx context.Context, r *Request) {
  2745  		w, err := r.Watch(ctx)
  2746  		if err == nil {
  2747  			// in this test the response body returned by the server is always empty,
  2748  			// this will cause StreamWatcher.receive() to:
  2749  			// - return an io.EOF to indicate that the watch closed normally and
  2750  			// - then close the io.Reader
  2751  			// since we assert on the number of times 'Close' has been called on the
  2752  			// body of the response object, we need to wait here to avoid race condition.
  2753  			<-w.ResultChan()
  2754  		}
  2755  	})
  2756  }
  2757  
  2758  func TestRequestDoRetryWithRateLimiterBackoffAndMetrics(t *testing.T) {
  2759  	// both request.Do and request.DoRaw have the same behavior and expectations
  2760  	testRetryWithRateLimiterBackoffAndMetrics(t, "Do", func(ctx context.Context, r *Request) {
  2761  		r.DoRaw(ctx)
  2762  	})
  2763  }
  2764  
  2765  func TestRequestStreamRetryWithRateLimiterBackoffAndMetrics(t *testing.T) {
  2766  	testRetryWithRateLimiterBackoffAndMetrics(t, "Stream", func(ctx context.Context, r *Request) {
  2767  		r.Stream(ctx)
  2768  	})
  2769  }
  2770  
  2771  func TestRequestWatchRetryWithRateLimiterBackoffAndMetrics(t *testing.T) {
  2772  	testRetryWithRateLimiterBackoffAndMetrics(t, "Watch", func(ctx context.Context, r *Request) {
  2773  		w, err := r.Watch(ctx)
  2774  		if err == nil {
  2775  			// in this test the response body returned by the server is always empty,
  2776  			// this will cause StreamWatcher.receive() to:
  2777  			// - return an io.EOF to indicate that the watch closed normally and
  2778  			// - then close the io.Reader
  2779  			// since we assert on the number of times 'Close' has been called on the
  2780  			// body of the response object, we need to wait here to avoid race condition.
  2781  			<-w.ResultChan()
  2782  		}
  2783  	})
  2784  }
  2785  
  2786  func TestRequestDoWithRetryInvokeOrder(t *testing.T) {
  2787  	// both request.Do and request.DoRaw have the same behavior and expectations
  2788  	testWithRetryInvokeOrder(t, "Do", func(ctx context.Context, r *Request) {
  2789  		r.DoRaw(ctx)
  2790  	})
  2791  }
  2792  
  2793  func TestRequestStreamWithRetryInvokeOrder(t *testing.T) {
  2794  	testWithRetryInvokeOrder(t, "Stream", func(ctx context.Context, r *Request) {
  2795  		r.Stream(ctx)
  2796  	})
  2797  }
  2798  
  2799  func TestRequestWatchWithRetryInvokeOrder(t *testing.T) {
  2800  	testWithRetryInvokeOrder(t, "Watch", func(ctx context.Context, r *Request) {
  2801  		w, err := r.Watch(ctx)
  2802  		if err == nil {
  2803  			// in this test the response body returned by the server is always empty,
  2804  			// this will cause StreamWatcher.receive() to:
  2805  			// - return an io.EOF to indicate that the watch closed normally and
  2806  			// - then close the io.Reader
  2807  			// since we assert on the number of times 'Close' has been called on the
  2808  			// body of the response object, we need to wait here to avoid race condition.
  2809  			<-w.ResultChan()
  2810  		}
  2811  	})
  2812  }
  2813  
  2814  func TestRequestWatchWithWrapPreviousError(t *testing.T) {
  2815  	testWithWrapPreviousError(t, func(ctx context.Context, r *Request) error {
  2816  		w, err := r.Watch(ctx)
  2817  		if err == nil {
  2818  			// in this test the response body returned by the server is always empty,
  2819  			// this will cause StreamWatcher.receive() to:
  2820  			// - return an io.EOF to indicate that the watch closed normally and
  2821  			// - then close the io.Reader
  2822  			// since we assert on the number of times 'Close' has been called on the
  2823  			// body of the response object, we need to wait here to avoid race condition.
  2824  			<-w.ResultChan()
  2825  		}
  2826  		return err
  2827  	})
  2828  }
  2829  
  2830  func TestRequestDoWithWrapPreviousError(t *testing.T) {
  2831  	// both request.Do and request.DoRaw have the same behavior and expectations
  2832  	testWithWrapPreviousError(t, func(ctx context.Context, r *Request) error {
  2833  		result := r.Do(ctx)
  2834  		return result.err
  2835  	})
  2836  }
  2837  
  2838  func testRequestWithRetry(t *testing.T, key string, doFunc func(ctx context.Context, r *Request)) {
  2839  	type expected struct {
  2840  		attempts  int
  2841  		reqCount  *count
  2842  		respCount *count
  2843  	}
  2844  
  2845  	tests := []struct {
  2846  		name          string
  2847  		verb          string
  2848  		body          io.Reader
  2849  		bodyBytes     []byte
  2850  		maxRetries    int
  2851  		serverReturns []responseErr
  2852  
  2853  		// expectations differ based on whether it is 'Watch', 'Stream' or 'Do'
  2854  		expectations map[string]expected
  2855  	}{
  2856  		{
  2857  			name:       "server always returns retry-after response",
  2858  			verb:       "GET",
  2859  			bodyBytes:  []byte{},
  2860  			maxRetries: 2,
  2861  			serverReturns: []responseErr{
  2862  				{response: retryAfterResponse(), err: nil},
  2863  				{response: retryAfterResponse(), err: nil},
  2864  				{response: retryAfterResponse(), err: nil},
  2865  			},
  2866  			expectations: map[string]expected{
  2867  				"Do": {
  2868  					attempts:  3,
  2869  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2870  					respCount: &count{closes: 3, seeks: []seek{}},
  2871  				},
  2872  				"Watch": {
  2873  					attempts:  3,
  2874  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2875  					respCount: &count{closes: 3, seeks: []seek{}},
  2876  				},
  2877  				"Stream": {
  2878  					attempts:  3,
  2879  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2880  					respCount: &count{closes: 3, seeks: []seek{}},
  2881  				},
  2882  			},
  2883  		},
  2884  		{
  2885  			name:       "server always returns retryable error",
  2886  			verb:       "GET",
  2887  			bodyBytes:  []byte{},
  2888  			maxRetries: 2,
  2889  			serverReturns: []responseErr{
  2890  				{response: nil, err: io.EOF},
  2891  				{response: nil, err: io.EOF},
  2892  				{response: nil, err: io.EOF},
  2893  			},
  2894  			expectations: map[string]expected{
  2895  				"Do": {
  2896  					attempts:  3,
  2897  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2898  					respCount: &count{closes: 0, seeks: []seek{}},
  2899  				},
  2900  				"Watch": {
  2901  					attempts:  3,
  2902  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2903  					respCount: &count{closes: 0, seeks: []seek{}},
  2904  				},
  2905  				// for Stream, we never retry on any error
  2906  				"Stream": {
  2907  					attempts:  1, // only the first attempt is expected
  2908  					reqCount:  &count{closes: 0, seeks: []seek{}},
  2909  					respCount: &count{closes: 0, seeks: []seek{}},
  2910  				},
  2911  			},
  2912  		},
  2913  		{
  2914  			name:       "server returns success on the final retry",
  2915  			verb:       "GET",
  2916  			bodyBytes:  []byte{},
  2917  			maxRetries: 2,
  2918  			serverReturns: []responseErr{
  2919  				{response: retryAfterResponse(), err: nil},
  2920  				{response: nil, err: io.EOF},
  2921  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  2922  			},
  2923  			expectations: map[string]expected{
  2924  				"Do": {
  2925  					attempts:  3,
  2926  					reqCount:  &count{closes: 0, seeks: make([]seek, 2)},
  2927  					respCount: &count{closes: 2, seeks: []seek{}},
  2928  				},
  2929  				"Watch": {
  2930  					attempts: 3,
  2931  					reqCount: &count{closes: 0, seeks: make([]seek, 2)},
  2932  					// the Body of the successful response object will get closed by
  2933  					// StreamWatcher, so we need to take that into account.
  2934  					respCount: &count{closes: 2, seeks: []seek{}},
  2935  				},
  2936  				"Stream": {
  2937  					attempts:  2,
  2938  					reqCount:  &count{closes: 0, seeks: make([]seek, 1)},
  2939  					respCount: &count{closes: 1, seeks: []seek{}},
  2940  				},
  2941  			},
  2942  		},
  2943  	}
  2944  
  2945  	for _, test := range tests {
  2946  		t.Run(test.name, func(t *testing.T) {
  2947  			respCountGot := newCount()
  2948  			responseRecorder := newReadTracker(respCountGot)
  2949  			var attempts int
  2950  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  2951  				defer func() {
  2952  					attempts++
  2953  				}()
  2954  
  2955  				resp := test.serverReturns[attempts].response
  2956  				if resp != nil {
  2957  					responseRecorder.delegated = io.NopCloser(bytes.NewReader([]byte{}))
  2958  					resp.Body = responseRecorder
  2959  				}
  2960  				return resp, test.serverReturns[attempts].err
  2961  			})
  2962  
  2963  			req := &Request{
  2964  				verb:      test.verb,
  2965  				body:      test.body,
  2966  				bodyBytes: test.bodyBytes,
  2967  				c: &RESTClient{
  2968  					content: defaultContentConfig(),
  2969  					Client:  client,
  2970  				},
  2971  				backoff:    &noSleepBackOff{},
  2972  				maxRetries: test.maxRetries,
  2973  				retryFn:    defaultRequestRetryFn,
  2974  			}
  2975  
  2976  			doFunc(context.Background(), req)
  2977  
  2978  			expected, ok := test.expectations[key]
  2979  			if !ok {
  2980  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  2981  			}
  2982  			if expected.attempts != attempts {
  2983  				t.Errorf("Expected retries: %d, but got: %d", expected.attempts, attempts)
  2984  			}
  2985  
  2986  			if expected.respCount.closes != respCountGot.getCloseCount() {
  2987  				t.Errorf("Expected response body Close to be invoked %d times, but got: %d", expected.respCount.closes, respCountGot.getCloseCount())
  2988  			}
  2989  		})
  2990  	}
  2991  }
  2992  
  2993  type retryTestKeyType int
  2994  
  2995  const retryTestKey retryTestKeyType = iota
  2996  
  2997  // fake flowcontrol.RateLimiter so we can tap into the Wait method of the rate limiter.
  2998  // fake BackoffManager so we can tap into backoff calls
  2999  // fake metrics.ResultMetric to tap into the metric calls
  3000  // we use it to verify that RateLimiter, BackoffManager, and
  3001  // metric calls are invoked appropriately in right order.
  3002  type withRateLimiterBackoffManagerAndMetrics struct {
  3003  	flowcontrol.RateLimiter
  3004  	*NoBackoff
  3005  	metrics.ResultMetric
  3006  	calculateBackoffSeq int64
  3007  	calculateBackoffFn  func(i int64) time.Duration
  3008  	metrics.RetryMetric
  3009  
  3010  	invokeOrderGot []string
  3011  	sleepsGot      []string
  3012  	statusCodesGot []string
  3013  }
  3014  
  3015  func (lb *withRateLimiterBackoffManagerAndMetrics) Wait(ctx context.Context) error {
  3016  	lb.invokeOrderGot = append(lb.invokeOrderGot, "RateLimiter.Wait")
  3017  	return nil
  3018  }
  3019  
  3020  func (lb *withRateLimiterBackoffManagerAndMetrics) CalculateBackoff(actualUrl *url.URL) time.Duration {
  3021  	lb.invokeOrderGot = append(lb.invokeOrderGot, "BackoffManager.CalculateBackoff")
  3022  
  3023  	waitFor := lb.calculateBackoffFn(lb.calculateBackoffSeq)
  3024  	lb.calculateBackoffSeq++
  3025  	return waitFor
  3026  }
  3027  
  3028  func (lb *withRateLimiterBackoffManagerAndMetrics) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) {
  3029  	lb.invokeOrderGot = append(lb.invokeOrderGot, "BackoffManager.UpdateBackoff")
  3030  }
  3031  
  3032  func (lb *withRateLimiterBackoffManagerAndMetrics) Sleep(d time.Duration) {
  3033  	lb.invokeOrderGot = append(lb.invokeOrderGot, "BackoffManager.Sleep")
  3034  	lb.sleepsGot = append(lb.sleepsGot, d.String())
  3035  }
  3036  
  3037  func (lb *withRateLimiterBackoffManagerAndMetrics) Increment(ctx context.Context, code, _, _ string) {
  3038  	// we are interested in the request context that is marked by this test
  3039  	if marked, ok := ctx.Value(retryTestKey).(bool); ok && marked {
  3040  		lb.invokeOrderGot = append(lb.invokeOrderGot, "RequestResult.Increment")
  3041  		lb.statusCodesGot = append(lb.statusCodesGot, code)
  3042  	}
  3043  }
  3044  
  3045  func (lb *withRateLimiterBackoffManagerAndMetrics) IncrementRetry(ctx context.Context, code, _, _ string) {
  3046  	// we are interested in the request context that is marked by this test
  3047  	if marked, ok := ctx.Value(retryTestKey).(bool); ok && marked {
  3048  		lb.invokeOrderGot = append(lb.invokeOrderGot, "RequestRetry.IncrementRetry")
  3049  		lb.statusCodesGot = append(lb.statusCodesGot, code)
  3050  	}
  3051  }
  3052  
  3053  func (lb *withRateLimiterBackoffManagerAndMetrics) Do() {
  3054  	lb.invokeOrderGot = append(lb.invokeOrderGot, "Client.Do")
  3055  }
  3056  
  3057  func testRetryWithRateLimiterBackoffAndMetrics(t *testing.T, key string, doFunc func(ctx context.Context, r *Request)) {
  3058  	type expected struct {
  3059  		attempts    int
  3060  		order       []string
  3061  		sleeps      []string
  3062  		statusCodes []string
  3063  	}
  3064  
  3065  	// we define the expected order of how the client invokes the
  3066  	// rate limiter, backoff, and metrics methods.
  3067  	// scenario:
  3068  	//  - A: original request fails with a retryable response: (500, 'Retry-After: N')
  3069  	//  - B: retry 1: successful with a status code 200
  3070  	// so we have a total of 2 attempts
  3071  	invokeOrderWant := []string{
  3072  		// before we send the request to the server:
  3073  		// - we wait as dictated by the client rate lmiter
  3074  		// - we wait, as dictated by the backoff manager
  3075  		"RateLimiter.Wait",
  3076  		"BackoffManager.CalculateBackoff",
  3077  		"BackoffManager.Sleep",
  3078  
  3079  		// A: first attempt for which the server sends a retryable response
  3080  		// status code: 500, Retry-Afer: N
  3081  		"Client.Do",
  3082  
  3083  		// we got a response object, status code: 500, Retry-Afer: N
  3084  		//  - call metrics method with appropriate status code
  3085  		//  - update backoff parameters with the status code returned
  3086  		"RequestResult.Increment",
  3087  		"BackoffManager.UpdateBackoff",
  3088  		"BackoffManager.CalculateBackoff",
  3089  		// sleep for delay=max(BackoffManager.CalculateBackoff, Retry-After: N)
  3090  		"BackoffManager.Sleep",
  3091  		// wait as dictated by the client rate lmiter
  3092  		"RateLimiter.Wait",
  3093  
  3094  		// B: 2nd attempt: retry, and this should return a status code=200
  3095  		"Client.Do",
  3096  
  3097  		// it's a success, so do the following:
  3098  		// count the result metric, and since it's a retry,
  3099  		// count the retry metric, and then update backoff parameters.
  3100  		"RequestResult.Increment",
  3101  		"RequestRetry.IncrementRetry",
  3102  		"BackoffManager.UpdateBackoff",
  3103  	}
  3104  	statusCodesWant := []string{
  3105  		// first attempt (A): we count the result metric only
  3106  		"500",
  3107  		// final attempt (B): we count the result metric, and the retry metric
  3108  		"200", "200",
  3109  	}
  3110  
  3111  	tests := []struct {
  3112  		name               string
  3113  		maxRetries         int
  3114  		serverReturns      []responseErr
  3115  		calculateBackoffFn func(i int64) time.Duration
  3116  		// expectations differ based on whether it is 'Watch', 'Stream' or 'Do'
  3117  		expectations map[string]expected
  3118  	}{
  3119  		{
  3120  			name:       "success after one retry, Retry-After: N > BackoffManager.CalculateBackoff",
  3121  			maxRetries: 1,
  3122  			serverReturns: []responseErr{
  3123  				{response: retryAfterResponseWithDelay("5"), err: nil},
  3124  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3125  			},
  3126  			// we simulate a sleep sequence of 0s, 1s, 2s, 3s, ...
  3127  			calculateBackoffFn: func(i int64) time.Duration { return time.Duration(i * int64(time.Second)) },
  3128  			expectations: map[string]expected{
  3129  				"Do": {
  3130  					attempts:    2,
  3131  					order:       invokeOrderWant,
  3132  					statusCodes: statusCodesWant,
  3133  					sleeps: []string{
  3134  						// initial backoff.Sleep before we send the request to the server for the first time
  3135  						"0s",
  3136  						// maximum of:
  3137  						//  - 'Retry-After: 5' response header from (A)
  3138  						//  - BackoffManager.CalculateBackoff (will return 1s)
  3139  						(5 * time.Second).String(),
  3140  					},
  3141  				},
  3142  				"Watch": {
  3143  					attempts: 2,
  3144  					// Watch does not do 'RateLimiter.Wait' before initially sending the request to the server
  3145  					order:       invokeOrderWant[1:],
  3146  					statusCodes: statusCodesWant,
  3147  					sleeps: []string{
  3148  						"0s",
  3149  						(5 * time.Second).String(),
  3150  					},
  3151  				},
  3152  				"Stream": {
  3153  					attempts:    2,
  3154  					order:       invokeOrderWant,
  3155  					statusCodes: statusCodesWant,
  3156  					sleeps: []string{
  3157  						"0s",
  3158  						(5 * time.Second).String(),
  3159  					},
  3160  				},
  3161  			},
  3162  		},
  3163  		{
  3164  			name:       "success after one retry, Retry-After: N < BackoffManager.CalculateBackoff",
  3165  			maxRetries: 1,
  3166  			serverReturns: []responseErr{
  3167  				{response: retryAfterResponseWithDelay("2"), err: nil},
  3168  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3169  			},
  3170  			// we simulate a sleep sequence of 0s, 4s, 8s, 16s, ...
  3171  			calculateBackoffFn: func(i int64) time.Duration { return time.Duration(i * int64(4*time.Second)) },
  3172  			expectations: map[string]expected{
  3173  				"Do": {
  3174  					attempts:    2,
  3175  					order:       invokeOrderWant,
  3176  					statusCodes: statusCodesWant,
  3177  					sleeps: []string{
  3178  						// initial backoff.Sleep before we send the request to the server for the first time
  3179  						"0s",
  3180  						// maximum of:
  3181  						//  - 'Retry-After: 2' response header from (A)
  3182  						//  - BackoffManager.CalculateBackoff (will return 4s)
  3183  						(4 * time.Second).String(),
  3184  					},
  3185  				},
  3186  				"Watch": {
  3187  					attempts: 2,
  3188  					// Watch does not do 'RateLimiter.Wait' before initially sending the request to the server
  3189  					order:       invokeOrderWant[1:],
  3190  					statusCodes: statusCodesWant,
  3191  					sleeps: []string{
  3192  						"0s",
  3193  						(4 * time.Second).String(),
  3194  					},
  3195  				},
  3196  				"Stream": {
  3197  					attempts:    2,
  3198  					order:       invokeOrderWant,
  3199  					statusCodes: statusCodesWant,
  3200  					sleeps: []string{
  3201  						"0s",
  3202  						(4 * time.Second).String(),
  3203  					},
  3204  				},
  3205  			},
  3206  		},
  3207  	}
  3208  
  3209  	for _, test := range tests {
  3210  		t.Run(test.name, func(t *testing.T) {
  3211  			interceptor := &withRateLimiterBackoffManagerAndMetrics{
  3212  				RateLimiter:        flowcontrol.NewFakeAlwaysRateLimiter(),
  3213  				NoBackoff:          &NoBackoff{},
  3214  				calculateBackoffFn: test.calculateBackoffFn,
  3215  			}
  3216  
  3217  			// TODO: today this is the only site where a test overrides the
  3218  			//  default metric interfaces, in future if we other tests want
  3219  			//  to override as well, and we want tests to be able to run in
  3220  			//  parallel then we will need to provide a way for tests to
  3221  			//  register/deregister their own metric inerfaces.
  3222  			oldRequestResult := metrics.RequestResult
  3223  			oldRequestRetry := metrics.RequestRetry
  3224  			metrics.RequestResult = interceptor
  3225  			metrics.RequestRetry = interceptor
  3226  			defer func() {
  3227  				metrics.RequestResult = oldRequestResult
  3228  				metrics.RequestRetry = oldRequestRetry
  3229  			}()
  3230  
  3231  			ctx, cancel := context.WithCancel(context.Background())
  3232  			defer cancel()
  3233  			// we are changing metrics.RequestResult (a global state) in
  3234  			// this test, to avoid interference from other tests running in
  3235  			// parallel we need to associate a key to the context so we
  3236  			// can identify the metric calls associated with this test.
  3237  			ctx = context.WithValue(ctx, retryTestKey, true)
  3238  
  3239  			var attempts int
  3240  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  3241  				defer func() {
  3242  					attempts++
  3243  				}()
  3244  
  3245  				interceptor.Do()
  3246  				resp := test.serverReturns[attempts].response
  3247  				if resp != nil {
  3248  					resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
  3249  				}
  3250  				return resp, test.serverReturns[attempts].err
  3251  			})
  3252  
  3253  			base, err := url.Parse("http://foo.bar")
  3254  			if err != nil {
  3255  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  3256  			}
  3257  			req := &Request{
  3258  				verb:      "GET",
  3259  				bodyBytes: []byte{},
  3260  				c: &RESTClient{
  3261  					base:        base,
  3262  					content:     defaultContentConfig(),
  3263  					Client:      client,
  3264  					rateLimiter: interceptor,
  3265  				},
  3266  				pathPrefix:  "/api/v1",
  3267  				rateLimiter: interceptor,
  3268  				backoff:     interceptor,
  3269  				maxRetries:  test.maxRetries,
  3270  				retryFn:     defaultRequestRetryFn,
  3271  			}
  3272  
  3273  			doFunc(ctx, req)
  3274  
  3275  			want, ok := test.expectations[key]
  3276  			if !ok {
  3277  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  3278  			}
  3279  			if want.attempts != attempts {
  3280  				t.Errorf("%s: Expected retries: %d, but got: %d", key, want.attempts, attempts)
  3281  			}
  3282  			if !cmp.Equal(want.order, interceptor.invokeOrderGot) {
  3283  				t.Errorf("%s: Expected invoke order to match, diff: %s", key, cmp.Diff(want.order, interceptor.invokeOrderGot))
  3284  			}
  3285  			if !cmp.Equal(want.sleeps, interceptor.sleepsGot) {
  3286  				t.Errorf("%s: Expected sleep sequence to match, diff: %s", key, cmp.Diff(want.sleeps, interceptor.sleepsGot))
  3287  			}
  3288  			if !cmp.Equal(want.statusCodes, interceptor.statusCodesGot) {
  3289  				t.Errorf("%s: Expected status codes to match, diff: %s", key, cmp.Diff(want.statusCodes, interceptor.statusCodesGot))
  3290  			}
  3291  		})
  3292  	}
  3293  }
  3294  
  3295  type retryInterceptor struct {
  3296  	WithRetry
  3297  	invokeOrderGot []string
  3298  }
  3299  
  3300  func (ri *retryInterceptor) IsNextRetry(ctx context.Context, restReq *Request, httpReq *http.Request, resp *http.Response, err error, f IsRetryableErrorFunc) bool {
  3301  	ri.invokeOrderGot = append(ri.invokeOrderGot, "WithRetry.IsNextRetry")
  3302  	return ri.WithRetry.IsNextRetry(ctx, restReq, httpReq, resp, err, f)
  3303  }
  3304  
  3305  func (ri *retryInterceptor) Before(ctx context.Context, request *Request) error {
  3306  	ri.invokeOrderGot = append(ri.invokeOrderGot, "WithRetry.Before")
  3307  	return ri.WithRetry.Before(ctx, request)
  3308  }
  3309  
  3310  func (ri *retryInterceptor) After(ctx context.Context, request *Request, resp *http.Response, err error) {
  3311  	ri.invokeOrderGot = append(ri.invokeOrderGot, "WithRetry.After")
  3312  	ri.WithRetry.After(ctx, request, resp, err)
  3313  }
  3314  
  3315  func (ri *retryInterceptor) Do() {
  3316  	ri.invokeOrderGot = append(ri.invokeOrderGot, "Client.Do")
  3317  }
  3318  
  3319  func testWithRetryInvokeOrder(t *testing.T, key string, doFunc func(ctx context.Context, r *Request)) {
  3320  	// we define the expected order of how the client
  3321  	// should invoke the retry interface
  3322  	// scenario:
  3323  	//  - A: original request fails with a retryable response: (500, 'Retry-After: 1')
  3324  	//  - B: retry 1: successful with a status code 200
  3325  	// so we have a total of 2 attempts
  3326  	defaultInvokeOrderWant := []string{
  3327  		// first attempt (A)
  3328  		"WithRetry.Before",
  3329  		"Client.Do",
  3330  		"WithRetry.After",
  3331  		// server returns a retryable response: (500, 'Retry-After: 1')
  3332  		// IsNextRetry is expected to return true
  3333  		"WithRetry.IsNextRetry",
  3334  
  3335  		// second attempt (B) - retry 1: successful with a status code 200
  3336  		"WithRetry.Before",
  3337  		"Client.Do",
  3338  		"WithRetry.After",
  3339  		// success: IsNextRetry is expected to return false
  3340  		// Watch and Stream are an exception, they return as soon as the
  3341  		// server sends a status code of success.
  3342  		"WithRetry.IsNextRetry",
  3343  	}
  3344  
  3345  	tests := []struct {
  3346  		name          string
  3347  		maxRetries    int
  3348  		serverReturns []responseErr
  3349  		// expectations differ based on whether it is 'Watch', 'Stream' or 'Do'
  3350  		expectations map[string][]string
  3351  	}{
  3352  		{
  3353  			name:       "success after one retry",
  3354  			maxRetries: 1,
  3355  			serverReturns: []responseErr{
  3356  				{response: retryAfterResponse(), err: nil},
  3357  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3358  			},
  3359  			expectations: map[string][]string{
  3360  				"Do": defaultInvokeOrderWant,
  3361  				// Watch and Stream skip the final 'IsNextRetry' by returning
  3362  				// as soon as they see a success from the server.
  3363  				"Watch":  defaultInvokeOrderWant[0 : len(defaultInvokeOrderWant)-1],
  3364  				"Stream": defaultInvokeOrderWant[0 : len(defaultInvokeOrderWant)-1],
  3365  			},
  3366  		},
  3367  	}
  3368  
  3369  	for _, test := range tests {
  3370  		t.Run(test.name, func(t *testing.T) {
  3371  			interceptor := &retryInterceptor{
  3372  				WithRetry: &withRetry{maxRetries: test.maxRetries},
  3373  			}
  3374  
  3375  			var attempts int
  3376  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  3377  				defer func() {
  3378  					attempts++
  3379  				}()
  3380  
  3381  				interceptor.Do()
  3382  				resp := test.serverReturns[attempts].response
  3383  				if resp != nil {
  3384  					resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
  3385  				}
  3386  				return resp, test.serverReturns[attempts].err
  3387  			})
  3388  
  3389  			base, err := url.Parse("http://foo.bar")
  3390  			if err != nil {
  3391  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  3392  			}
  3393  			req := &Request{
  3394  				verb:      "GET",
  3395  				bodyBytes: []byte{},
  3396  				c: &RESTClient{
  3397  					base:    base,
  3398  					content: defaultContentConfig(),
  3399  					Client:  client,
  3400  				},
  3401  				pathPrefix:  "/api/v1",
  3402  				rateLimiter: flowcontrol.NewFakeAlwaysRateLimiter(),
  3403  				backoff:     &NoBackoff{},
  3404  				retryFn:     func(_ int) WithRetry { return interceptor },
  3405  			}
  3406  
  3407  			doFunc(context.Background(), req)
  3408  
  3409  			if attempts != 2 {
  3410  				t.Errorf("%s: Expected attempts: %d, but got: %d", key, 2, attempts)
  3411  			}
  3412  			invokeOrderWant, ok := test.expectations[key]
  3413  			if !ok {
  3414  				t.Fatalf("Wrong test setup - did not find expected for: %s", key)
  3415  			}
  3416  			if !cmp.Equal(invokeOrderWant, interceptor.invokeOrderGot) {
  3417  				t.Errorf("%s: Expected invoke order to match, diff: %s", key, cmp.Diff(invokeOrderWant, interceptor.invokeOrderGot))
  3418  			}
  3419  		})
  3420  	}
  3421  }
  3422  
  3423  func testWithWrapPreviousError(t *testing.T, doFunc func(ctx context.Context, r *Request) error) {
  3424  	var (
  3425  		containsFormatExpected = "- error from a previous attempt: %s"
  3426  		nonRetryableErr        = errors.New("non retryable error")
  3427  	)
  3428  
  3429  	tests := []struct {
  3430  		name             string
  3431  		maxRetries       int
  3432  		serverReturns    []responseErr
  3433  		expectedErr      error
  3434  		wrapped          bool
  3435  		attemptsExpected int
  3436  		contains         string
  3437  	}{
  3438  		{
  3439  			name:       "success at first attempt",
  3440  			maxRetries: 2,
  3441  			serverReturns: []responseErr{
  3442  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3443  			},
  3444  			attemptsExpected: 1,
  3445  			expectedErr:      nil,
  3446  		},
  3447  		{
  3448  			name:       "success after a series of retry-after from the server",
  3449  			maxRetries: 2,
  3450  			serverReturns: []responseErr{
  3451  				{response: retryAfterResponse(), err: nil},
  3452  				{response: retryAfterResponse(), err: nil},
  3453  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3454  			},
  3455  			attemptsExpected: 3,
  3456  			expectedErr:      nil,
  3457  		},
  3458  		{
  3459  			name:       "success after a series of retryable errors",
  3460  			maxRetries: 2,
  3461  			serverReturns: []responseErr{
  3462  				{response: nil, err: io.EOF},
  3463  				{response: nil, err: io.EOF},
  3464  				{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3465  			},
  3466  			attemptsExpected: 3,
  3467  			expectedErr:      nil,
  3468  		},
  3469  		{
  3470  			name:       "request errors out with a non retryable error",
  3471  			maxRetries: 2,
  3472  			serverReturns: []responseErr{
  3473  				{response: nil, err: nonRetryableErr},
  3474  			},
  3475  			attemptsExpected: 1,
  3476  			expectedErr:      nonRetryableErr,
  3477  		},
  3478  		{
  3479  			name:       "request times out after retries, but no previous error",
  3480  			maxRetries: 2,
  3481  			serverReturns: []responseErr{
  3482  				{response: retryAfterResponse(), err: nil},
  3483  				{response: retryAfterResponse(), err: nil},
  3484  				{response: nil, err: context.Canceled},
  3485  			},
  3486  			attemptsExpected: 3,
  3487  			expectedErr:      context.Canceled,
  3488  		},
  3489  		{
  3490  			name:       "request times out after retries, and we have a relevant previous error",
  3491  			maxRetries: 3,
  3492  			serverReturns: []responseErr{
  3493  				{response: nil, err: io.EOF},
  3494  				{response: retryAfterResponse(), err: nil},
  3495  				{response: retryAfterResponse(), err: nil},
  3496  				{response: nil, err: context.Canceled},
  3497  			},
  3498  			attemptsExpected: 4,
  3499  			wrapped:          true,
  3500  			expectedErr:      context.Canceled,
  3501  			contains:         fmt.Sprintf(containsFormatExpected, io.EOF),
  3502  		},
  3503  		{
  3504  			name:       "interleaved retry-after responses with retryable errors",
  3505  			maxRetries: 8,
  3506  			serverReturns: []responseErr{
  3507  				{response: retryAfterResponse(), err: nil},
  3508  				{response: retryAfterResponse(), err: nil},
  3509  				{response: nil, err: io.ErrUnexpectedEOF},
  3510  				{response: retryAfterResponse(), err: nil},
  3511  				{response: retryAfterResponse(), err: nil},
  3512  				{response: nil, err: io.EOF},
  3513  				{response: retryAfterResponse(), err: nil},
  3514  				{response: retryAfterResponse(), err: nil},
  3515  				{response: nil, err: context.Canceled},
  3516  			},
  3517  			attemptsExpected: 9,
  3518  			wrapped:          true,
  3519  			expectedErr:      context.Canceled,
  3520  			contains:         fmt.Sprintf(containsFormatExpected, io.EOF),
  3521  		},
  3522  		{
  3523  			name:       "request errors out with a retryable error, followed by a non-retryable one",
  3524  			maxRetries: 3,
  3525  			serverReturns: []responseErr{
  3526  				{response: nil, err: io.EOF},
  3527  				{response: nil, err: nonRetryableErr},
  3528  			},
  3529  			attemptsExpected: 2,
  3530  			wrapped:          true,
  3531  			expectedErr:      nonRetryableErr,
  3532  			contains:         fmt.Sprintf(containsFormatExpected, io.EOF),
  3533  		},
  3534  		{
  3535  			name:       "use the most recent error",
  3536  			maxRetries: 2,
  3537  			serverReturns: []responseErr{
  3538  				{response: nil, err: io.ErrUnexpectedEOF},
  3539  				{response: nil, err: io.EOF},
  3540  				{response: nil, err: context.Canceled},
  3541  			},
  3542  			attemptsExpected: 3,
  3543  			wrapped:          true,
  3544  			expectedErr:      context.Canceled,
  3545  			contains:         fmt.Sprintf(containsFormatExpected, io.EOF),
  3546  		},
  3547  	}
  3548  
  3549  	for _, test := range tests {
  3550  		t.Run(test.name, func(t *testing.T) {
  3551  			var attempts int
  3552  			client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  3553  				defer func() {
  3554  					attempts++
  3555  				}()
  3556  
  3557  				resp := test.serverReturns[attempts].response
  3558  				if resp != nil {
  3559  					resp.Body = io.NopCloser(bytes.NewReader([]byte{}))
  3560  				}
  3561  				return resp, test.serverReturns[attempts].err
  3562  			})
  3563  
  3564  			base, err := url.Parse("http://foo.bar")
  3565  			if err != nil {
  3566  				t.Fatalf("Failed to create new HTTP request - %v", err)
  3567  			}
  3568  			req := &Request{
  3569  				verb:      "GET",
  3570  				bodyBytes: []byte{},
  3571  				c: &RESTClient{
  3572  					base:    base,
  3573  					content: defaultContentConfig(),
  3574  					Client:  client,
  3575  				},
  3576  				pathPrefix:  "/api/v1",
  3577  				rateLimiter: flowcontrol.NewFakeAlwaysRateLimiter(),
  3578  				backoff:     &noSleepBackOff{},
  3579  				maxRetries:  test.maxRetries,
  3580  				retryFn:     defaultRequestRetryFn,
  3581  			}
  3582  
  3583  			err = doFunc(context.Background(), req)
  3584  			if test.attemptsExpected != attempts {
  3585  				t.Errorf("Expected attempts: %d, but got: %d", test.attemptsExpected, attempts)
  3586  			}
  3587  
  3588  			switch {
  3589  			case test.expectedErr == nil:
  3590  				if err != nil {
  3591  					t.Errorf("Expected a nil error, but got: %v", err)
  3592  					return
  3593  				}
  3594  			case test.expectedErr != nil:
  3595  				if !strings.Contains(err.Error(), test.contains) {
  3596  					t.Errorf("Expected error message to contain %q, but got: %v", test.contains, err)
  3597  				}
  3598  
  3599  				urlErrGot, _ := err.(*url.Error)
  3600  				if test.wrapped {
  3601  					// we expect the url.Error from net/http to be wrapped by WrapPreviousError
  3602  					unwrapper, ok := err.(interface {
  3603  						Unwrap() error
  3604  					})
  3605  					if !ok {
  3606  						t.Errorf("Expected error to implement Unwrap method, but got: %v", err)
  3607  						return
  3608  					}
  3609  					urlErrGot, _ = unwrapper.Unwrap().(*url.Error)
  3610  				}
  3611  				// we always get a url.Error from net/http
  3612  				if urlErrGot == nil {
  3613  					t.Errorf("Expected error to be url.Error, but got: %v", err)
  3614  					return
  3615  				}
  3616  
  3617  				errGot := urlErrGot.Unwrap()
  3618  				if test.expectedErr != errGot {
  3619  					t.Errorf("Expected error %v, but got: %v", test.expectedErr, errGot)
  3620  				}
  3621  			}
  3622  		})
  3623  	}
  3624  }
  3625  
  3626  func TestReuseRequest(t *testing.T) {
  3627  	var tests = []struct {
  3628  		name        string
  3629  		enableHTTP2 bool
  3630  	}{
  3631  		{"HTTP1", false},
  3632  		{"HTTP2", true},
  3633  	}
  3634  	for _, tt := range tests {
  3635  		t.Run(tt.name, func(t *testing.T) {
  3636  
  3637  			ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3638  				w.Write([]byte(r.RemoteAddr))
  3639  			}))
  3640  			ts.EnableHTTP2 = tt.enableHTTP2
  3641  			ts.StartTLS()
  3642  			defer ts.Close()
  3643  
  3644  			ctx, cancel := context.WithCancel(context.Background())
  3645  			defer cancel()
  3646  
  3647  			c := testRESTClient(t, ts)
  3648  
  3649  			req1, err := c.Verb("GET").
  3650  				Prefix("foo").
  3651  				DoRaw(ctx)
  3652  			if err != nil {
  3653  				t.Fatalf("Unexpected error: %v", err)
  3654  			}
  3655  
  3656  			req2, err := c.Verb("GET").
  3657  				Prefix("foo").
  3658  				DoRaw(ctx)
  3659  			if err != nil {
  3660  				t.Fatalf("Unexpected error: %v", err)
  3661  			}
  3662  
  3663  			if string(req1) != string(req2) {
  3664  				t.Fatalf("Expected %v to be equal to %v", string(req1), string(req2))
  3665  			}
  3666  
  3667  		})
  3668  	}
  3669  }
  3670  
  3671  func TestHTTP1DoNotReuseRequestAfterTimeout(t *testing.T) {
  3672  	var tests = []struct {
  3673  		name        string
  3674  		enableHTTP2 bool
  3675  	}{
  3676  		{"HTTP1", false},
  3677  		{"HTTP2", true},
  3678  	}
  3679  	for _, tt := range tests {
  3680  		t.Run(tt.name, func(t *testing.T) {
  3681  			done := make(chan struct{})
  3682  			ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3683  				t.Logf("TEST Connected from %v on %v\n", r.RemoteAddr, r.URL.Path)
  3684  				if r.URL.Path == "/hang" {
  3685  					t.Logf("TEST hanging %v\n", r.RemoteAddr)
  3686  					<-done
  3687  				}
  3688  				w.Write([]byte(r.RemoteAddr))
  3689  			}))
  3690  			ts.EnableHTTP2 = tt.enableHTTP2
  3691  			ts.StartTLS()
  3692  			defer ts.Close()
  3693  			// close hanging connection before shutting down the http server
  3694  			defer close(done)
  3695  
  3696  			ctx, cancel := context.WithCancel(context.Background())
  3697  			defer cancel()
  3698  
  3699  			transport, ok := ts.Client().Transport.(*http.Transport)
  3700  			if !ok {
  3701  				t.Fatalf("failed to assert *http.Transport")
  3702  			}
  3703  
  3704  			config := &Config{
  3705  				Host:      ts.URL,
  3706  				Transport: utilnet.SetTransportDefaults(transport),
  3707  				Timeout:   1 * time.Second,
  3708  				// These fields are required to create a REST client.
  3709  				ContentConfig: ContentConfig{
  3710  					GroupVersion:         &schema.GroupVersion{},
  3711  					NegotiatedSerializer: &serializer.CodecFactory{},
  3712  				},
  3713  			}
  3714  			if !tt.enableHTTP2 {
  3715  				config.TLSClientConfig.NextProtos = []string{"http/1.1"}
  3716  			}
  3717  			c, err := RESTClientFor(config)
  3718  			if err != nil {
  3719  				t.Fatalf("failed to create REST client: %v", err)
  3720  			}
  3721  			req1, err := c.Verb("GET").
  3722  				Prefix("foo").
  3723  				DoRaw(ctx)
  3724  			if err != nil {
  3725  				t.Fatalf("Unexpected error: %v", err)
  3726  			}
  3727  
  3728  			_, err = c.Verb("GET").
  3729  				Prefix("/hang").
  3730  				DoRaw(ctx)
  3731  			if err == nil {
  3732  				t.Fatalf("Expected error")
  3733  			}
  3734  
  3735  			req2, err := c.Verb("GET").
  3736  				Prefix("foo").
  3737  				DoRaw(ctx)
  3738  			if err != nil {
  3739  				t.Fatalf("Unexpected error: %v", err)
  3740  			}
  3741  
  3742  			// http1 doesn't reuse the connection after it times
  3743  			if tt.enableHTTP2 != (string(req1) == string(req2)) {
  3744  				if tt.enableHTTP2 {
  3745  					t.Fatalf("Expected %v to be the same as %v", string(req1), string(req2))
  3746  				} else {
  3747  					t.Fatalf("Expected %v to be different to %v", string(req1), string(req2))
  3748  				}
  3749  			}
  3750  		})
  3751  	}
  3752  }
  3753  
  3754  func TestTransportConcurrency(t *testing.T) {
  3755  	const numReqs = 10
  3756  	var tests = []struct {
  3757  		name        string
  3758  		enableHTTP2 bool
  3759  	}{
  3760  		{"HTTP1", false},
  3761  		{"HTTP2", true},
  3762  	}
  3763  	for _, tt := range tests {
  3764  		t.Run(tt.name, func(t *testing.T) {
  3765  
  3766  			ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3767  				t.Logf("Connected from %v %v", r.RemoteAddr, r.URL)
  3768  				fmt.Fprintf(w, "%v", r.FormValue("echo"))
  3769  			}))
  3770  			ts.EnableHTTP2 = tt.enableHTTP2
  3771  			ts.StartTLS()
  3772  			defer ts.Close()
  3773  			var wg sync.WaitGroup
  3774  
  3775  			wg.Add(numReqs)
  3776  			c := testRESTClient(t, ts)
  3777  			reqs := make(chan string)
  3778  			defer close(reqs)
  3779  
  3780  			for i := 0; i < 4; i++ {
  3781  				go func() {
  3782  					for req := range reqs {
  3783  						res, err := c.Get().Param("echo", req).DoRaw(context.Background())
  3784  						if err != nil {
  3785  							t.Errorf("error on req %s: %v", req, err)
  3786  							wg.Done()
  3787  							continue
  3788  						}
  3789  
  3790  						if string(res) != req {
  3791  							t.Errorf("body of req %s = %q; want %q", req, res, req)
  3792  						}
  3793  
  3794  						wg.Done()
  3795  					}
  3796  				}()
  3797  			}
  3798  			for i := 0; i < numReqs; i++ {
  3799  				reqs <- fmt.Sprintf("request-%d", i)
  3800  			}
  3801  			wg.Wait()
  3802  		})
  3803  	}
  3804  }
  3805  
  3806  func TestRetryableConditions(t *testing.T) {
  3807  	var (
  3808  		methods = map[string]func(ctx context.Context, r *Request){
  3809  			"Do": func(ctx context.Context, r *Request) {
  3810  				r.Do(ctx)
  3811  			},
  3812  			"DoRaw": func(ctx context.Context, r *Request) {
  3813  				r.DoRaw(ctx)
  3814  			},
  3815  			"Stream": func(ctx context.Context, r *Request) {
  3816  				r.Stream(ctx)
  3817  			},
  3818  			"Watch": func(ctx context.Context, r *Request) {
  3819  				w, err := r.Watch(ctx)
  3820  				if err == nil {
  3821  					// we need to wait here to avoid race condition.
  3822  					<-w.ResultChan()
  3823  				}
  3824  			},
  3825  		}
  3826  
  3827  		alwaysRetry = map[string]bool{
  3828  			"Do":     true,
  3829  			"DoRaw":  true,
  3830  			"Watch":  true,
  3831  			"Stream": true,
  3832  		}
  3833  
  3834  		neverRetry = map[string]bool{
  3835  			"Do":     false,
  3836  			"DoRaw":  false,
  3837  			"Watch":  false,
  3838  			"Stream": false,
  3839  		}
  3840  
  3841  		alwaysRetryExceptStream = map[string]bool{
  3842  			"Do":     true,
  3843  			"DoRaw":  true,
  3844  			"Watch":  true,
  3845  			"Stream": false,
  3846  		}
  3847  	)
  3848  
  3849  	tests := []struct {
  3850  		name             string
  3851  		verbs            []string
  3852  		serverReturns    responseErr
  3853  		retryExpectation map[string]bool
  3854  	}{
  3855  		// {429, Retry-After: N} - we expect retry
  3856  		{
  3857  			name:             "server returns {429, Retry-After}",
  3858  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3859  			serverReturns:    responseErr{response: retryAfterResponseWithCodeAndDelay(http.StatusTooManyRequests, "0"), err: nil},
  3860  			retryExpectation: alwaysRetry,
  3861  		},
  3862  		// {5xx, Retry-After: N} - we expect retry
  3863  		{
  3864  			name:             "server returns {503, Retry-After}",
  3865  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3866  			serverReturns:    responseErr{response: retryAfterResponseWithCodeAndDelay(http.StatusServiceUnavailable, "0"), err: nil},
  3867  			retryExpectation: alwaysRetry,
  3868  		},
  3869  		// 5xx, but Retry-After: N is missing - no retry is expected
  3870  		{
  3871  			name:             "server returns 5xx, but no Retry-After",
  3872  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3873  			serverReturns:    responseErr{response: &http.Response{StatusCode: http.StatusInternalServerError}, err: nil},
  3874  			retryExpectation: neverRetry,
  3875  		},
  3876  		// 429, but Retry-After: N is missing - no retry is expected
  3877  		{
  3878  			name:             "server returns 429 but no Retry-After",
  3879  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3880  			serverReturns:    responseErr{response: &http.Response{StatusCode: http.StatusTooManyRequests}, err: nil},
  3881  			retryExpectation: neverRetry,
  3882  		},
  3883  		// response is nil, but error is set
  3884  		{
  3885  			name:             "server returns connection reset error",
  3886  			verbs:            []string{"GET"},
  3887  			serverReturns:    responseErr{response: nil, err: syscall.ECONNRESET},
  3888  			retryExpectation: alwaysRetryExceptStream,
  3889  		},
  3890  		{
  3891  			name:             "server returns EOF error",
  3892  			verbs:            []string{"GET"},
  3893  			serverReturns:    responseErr{response: nil, err: io.EOF},
  3894  			retryExpectation: alwaysRetryExceptStream,
  3895  		},
  3896  		{
  3897  			name:             "server returns unexpected EOF error",
  3898  			verbs:            []string{"GET"},
  3899  			serverReturns:    responseErr{response: nil, err: io.ErrUnexpectedEOF},
  3900  			retryExpectation: alwaysRetryExceptStream,
  3901  		},
  3902  		{
  3903  			name:             "server returns broken connection error",
  3904  			verbs:            []string{"GET"},
  3905  			serverReturns:    responseErr{response: nil, err: errors.New("http: can't write HTTP request on broken connection")},
  3906  			retryExpectation: alwaysRetryExceptStream,
  3907  		},
  3908  		{
  3909  			name:             "server returns GOAWAY error",
  3910  			verbs:            []string{"GET"},
  3911  			serverReturns:    responseErr{response: nil, err: errors.New("http2: server sent GOAWAY and closed the connection")},
  3912  			retryExpectation: alwaysRetryExceptStream,
  3913  		},
  3914  		{
  3915  			name:             "server returns connection reset by peer error",
  3916  			verbs:            []string{"GET"},
  3917  			serverReturns:    responseErr{response: nil, err: errors.New("connection reset by peer")},
  3918  			retryExpectation: alwaysRetryExceptStream,
  3919  		},
  3920  		{
  3921  			name:             "server returns use of closed network connection error",
  3922  			verbs:            []string{"GET"},
  3923  			serverReturns:    responseErr{response: nil, err: errors.New("use of closed network connection")},
  3924  			retryExpectation: alwaysRetryExceptStream,
  3925  		},
  3926  		// connection refused error never gets retried
  3927  		{
  3928  			name:             "server returns connection refused error",
  3929  			verbs:            []string{"GET"},
  3930  			serverReturns:    responseErr{response: nil, err: syscall.ECONNREFUSED},
  3931  			retryExpectation: neverRetry,
  3932  		},
  3933  		{
  3934  			name:             "server returns connection refused error",
  3935  			verbs:            []string{"POST"},
  3936  			serverReturns:    responseErr{response: nil, err: syscall.ECONNREFUSED},
  3937  			retryExpectation: neverRetry,
  3938  		},
  3939  		{
  3940  			name:          "server returns EOF error",
  3941  			verbs:         []string{"POST"},
  3942  			serverReturns: responseErr{response: nil, err: io.EOF},
  3943  			retryExpectation: map[string]bool{
  3944  				"Do":     false,
  3945  				"DoRaw":  false,
  3946  				"Watch":  true, // not applicable, Watch should always be GET only
  3947  				"Stream": false,
  3948  			},
  3949  		},
  3950  		// Timeout error gets retries by watch only
  3951  		{
  3952  			name:          "server returns net.Timeout() == true error",
  3953  			verbs:         []string{"GET"},
  3954  			serverReturns: responseErr{response: nil, err: &net.DNSError{IsTimeout: true}},
  3955  			retryExpectation: map[string]bool{
  3956  				"Do":     false,
  3957  				"DoRaw":  false,
  3958  				"Watch":  true,
  3959  				"Stream": false,
  3960  			},
  3961  		},
  3962  		{
  3963  			name:             "server returns OK, never retry",
  3964  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3965  			serverReturns:    responseErr{response: &http.Response{StatusCode: http.StatusOK}, err: nil},
  3966  			retryExpectation: neverRetry,
  3967  		},
  3968  		{
  3969  			name:             "server returns {3xx, Retry-After}",
  3970  			verbs:            []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
  3971  			serverReturns:    responseErr{response: &http.Response{StatusCode: http.StatusMovedPermanently, Header: http.Header{"Retry-After": []string{"0"}}}, err: nil},
  3972  			retryExpectation: neverRetry,
  3973  		},
  3974  	}
  3975  
  3976  	for _, test := range tests {
  3977  		for method, retryExpected := range test.retryExpectation {
  3978  			fn, ok := methods[method]
  3979  			if !ok {
  3980  				t.Fatalf("Wrong test setup, unknown method: %s", method)
  3981  			}
  3982  
  3983  			for _, verb := range test.verbs {
  3984  				t.Run(fmt.Sprintf("%s/%s/%s", test.name, method, verb), func(t *testing.T) {
  3985  					var attemptsGot int
  3986  					client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  3987  						attemptsGot++
  3988  						return test.serverReturns.response, test.serverReturns.err
  3989  					})
  3990  
  3991  					u, _ := url.Parse("http://localhost:123" + "/apis")
  3992  					req := &Request{
  3993  						verb: verb,
  3994  						c: &RESTClient{
  3995  							base:    u,
  3996  							content: defaultContentConfig(),
  3997  							Client:  client,
  3998  						},
  3999  						backoff:    &noSleepBackOff{},
  4000  						maxRetries: 2,
  4001  						retryFn:    defaultRequestRetryFn,
  4002  					}
  4003  
  4004  					fn(context.Background(), req)
  4005  
  4006  					if retryExpected {
  4007  						if attemptsGot != 3 {
  4008  							t.Errorf("Expected attempt count: %d, but got: %d", 3, attemptsGot)
  4009  						}
  4010  						return
  4011  					}
  4012  					// we don't expect retry, so we should see the first attempt only.
  4013  					if attemptsGot > 1 {
  4014  						t.Errorf("Expected no retry, but got %d attempts", attemptsGot)
  4015  					}
  4016  				})
  4017  			}
  4018  		}
  4019  	}
  4020  }
  4021  
  4022  func TestRequestConcurrencyWithRetry(t *testing.T) {
  4023  	var attempts int32
  4024  	client := clientForFunc(func(req *http.Request) (*http.Response, error) {
  4025  		defer func() {
  4026  			atomic.AddInt32(&attempts, 1)
  4027  		}()
  4028  
  4029  		// always send a retry-after response
  4030  		return &http.Response{
  4031  			StatusCode: http.StatusInternalServerError,
  4032  			Header:     http.Header{"Retry-After": []string{"1"}},
  4033  		}, nil
  4034  	})
  4035  
  4036  	req := &Request{
  4037  		verb: "POST",
  4038  		c: &RESTClient{
  4039  			content: defaultContentConfig(),
  4040  			Client:  client,
  4041  		},
  4042  		backoff:    &noSleepBackOff{},
  4043  		maxRetries: 9, // 10 attempts in total, including the first
  4044  		retryFn:    defaultRequestRetryFn,
  4045  	}
  4046  
  4047  	concurrency := 20
  4048  	wg := sync.WaitGroup{}
  4049  	wg.Add(concurrency)
  4050  	startCh := make(chan struct{})
  4051  	for i := 0; i < concurrency; i++ {
  4052  		go func() {
  4053  			defer wg.Done()
  4054  			<-startCh
  4055  			req.Do(context.Background())
  4056  		}()
  4057  	}
  4058  
  4059  	close(startCh)
  4060  	wg.Wait()
  4061  
  4062  	// we expect (concurrency*req.maxRetries+1) attempts to be recorded
  4063  	expected := concurrency * (req.maxRetries + 1)
  4064  	if atomic.LoadInt32(&attempts) != int32(expected) {
  4065  		t.Errorf("Expected attempts: %d, but got: %d", expected, attempts)
  4066  	}
  4067  }