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