1/* 2Copyright 2014 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package rest 18 19import ( 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 "syscall" 35 "testing" 36 "time" 37 38 "k8s.io/klog" 39 40 v1 "k8s.io/api/core/v1" 41 apiequality "k8s.io/apimachinery/pkg/api/equality" 42 apierrors "k8s.io/apimachinery/pkg/api/errors" 43 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 44 "k8s.io/apimachinery/pkg/runtime" 45 "k8s.io/apimachinery/pkg/runtime/schema" 46 "k8s.io/apimachinery/pkg/runtime/serializer/streaming" 47 "k8s.io/apimachinery/pkg/util/clock" 48 "k8s.io/apimachinery/pkg/util/diff" 49 "k8s.io/apimachinery/pkg/util/httpstream" 50 "k8s.io/apimachinery/pkg/util/intstr" 51 "k8s.io/apimachinery/pkg/watch" 52 "k8s.io/client-go/kubernetes/scheme" 53 restclientwatch "k8s.io/client-go/rest/watch" 54 "k8s.io/client-go/util/flowcontrol" 55 utiltesting "k8s.io/client-go/util/testing" 56) 57 58func TestNewRequestSetsAccept(t *testing.T) { 59 r := NewRequest(nil, "get", &url.URL{Path: "/path/"}, "", ContentConfig{}, Serializers{}, nil, nil, 0) 60 if r.headers.Get("Accept") != "" { 61 t.Errorf("unexpected headers: %#v", r.headers) 62 } 63 r = NewRequest(nil, "get", &url.URL{Path: "/path/"}, "", ContentConfig{ContentType: "application/other"}, Serializers{}, nil, nil, 0) 64 if r.headers.Get("Accept") != "application/other, */*" { 65 t.Errorf("unexpected headers: %#v", r.headers) 66 } 67} 68 69type clientFunc func(req *http.Request) (*http.Response, error) 70 71func (f clientFunc) Do(req *http.Request) (*http.Response, error) { 72 return f(req) 73} 74 75func TestRequestSetsHeaders(t *testing.T) { 76 server := clientFunc(func(req *http.Request) (*http.Response, error) { 77 if req.Header.Get("Accept") != "application/other, */*" { 78 t.Errorf("unexpected headers: %#v", req.Header) 79 } 80 return &http.Response{ 81 StatusCode: http.StatusForbidden, 82 Body: ioutil.NopCloser(bytes.NewReader([]byte{})), 83 }, nil 84 }) 85 config := defaultContentConfig() 86 config.ContentType = "application/other" 87 serializers := defaultSerializers(t) 88 r := NewRequest(server, "get", &url.URL{Path: "/path"}, "", config, serializers, nil, nil, 0) 89 90 // Check if all "issue" methods are setting headers. 91 _ = r.Do() 92 _, _ = r.Watch() 93 _, _ = r.Stream() 94} 95 96func TestRequestWithErrorWontChange(t *testing.T) { 97 gvCopy := v1.SchemeGroupVersion 98 original := Request{ 99 err: errors.New("test"), 100 content: ContentConfig{GroupVersion: &gvCopy}, 101 } 102 r := original 103 changed := r.Param("foo", "bar"). 104 AbsPath("/abs"). 105 Prefix("test"). 106 Suffix("testing"). 107 Namespace("new"). 108 Resource("foos"). 109 Name("bars"). 110 Body("foo"). 111 Timeout(time.Millisecond) 112 if changed != &r { 113 t.Errorf("returned request should point to the same object") 114 } 115 if !reflect.DeepEqual(changed, &original) { 116 t.Errorf("expected %#v, got %#v", &original, changed) 117 } 118} 119 120func TestRequestPreservesBaseTrailingSlash(t *testing.T) { 121 r := &Request{baseURL: &url.URL{}, pathPrefix: "/path/"} 122 if s := r.URL().String(); s != "/path/" { 123 t.Errorf("trailing slash should be preserved: %s", s) 124 } 125} 126 127func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) { 128 r := (&Request{baseURL: &url.URL{}}).AbsPath("/foo/") 129 if s := r.URL().String(); s != "/foo/" { 130 t.Errorf("trailing slash should be preserved: %s", s) 131 } 132 133 r = (&Request{baseURL: &url.URL{}}).AbsPath("/foo/") 134 if s := r.URL().String(); s != "/foo/" { 135 t.Errorf("trailing slash should be preserved: %s", s) 136 } 137} 138 139func TestRequestAbsPathJoins(t *testing.T) { 140 r := (&Request{baseURL: &url.URL{}}).AbsPath("foo/bar", "baz") 141 if s := r.URL().String(); s != "foo/bar/baz" { 142 t.Errorf("trailing slash should be preserved: %s", s) 143 } 144} 145 146func TestRequestSetsNamespace(t *testing.T) { 147 r := (&Request{ 148 baseURL: &url.URL{ 149 Path: "/", 150 }, 151 }).Namespace("foo") 152 if r.namespace == "" { 153 t.Errorf("namespace should be set: %#v", r) 154 } 155 156 if s := r.URL().String(); s != "namespaces/foo" { 157 t.Errorf("namespace should be in path: %s", s) 158 } 159} 160 161func TestRequestOrdersNamespaceInPath(t *testing.T) { 162 r := (&Request{ 163 baseURL: &url.URL{}, 164 pathPrefix: "/test/", 165 }).Name("bar").Resource("baz").Namespace("foo") 166 if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar" { 167 t.Errorf("namespace should be in order in path: %s", s) 168 } 169} 170 171func TestRequestOrdersSubResource(t *testing.T) { 172 r := (&Request{ 173 baseURL: &url.URL{}, 174 pathPrefix: "/test/", 175 }).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b") 176 if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar/a/b/test" { 177 t.Errorf("namespace should be in order in path: %s", s) 178 } 179} 180 181func TestRequestSetTwiceError(t *testing.T) { 182 if (&Request{}).Name("bar").Name("baz").err == nil { 183 t.Errorf("setting name twice should result in error") 184 } 185 if (&Request{}).Namespace("bar").Namespace("baz").err == nil { 186 t.Errorf("setting namespace twice should result in error") 187 } 188 if (&Request{}).Resource("bar").Resource("baz").err == nil { 189 t.Errorf("setting resource twice should result in error") 190 } 191 if (&Request{}).SubResource("bar").SubResource("baz").err == nil { 192 t.Errorf("setting subresource twice should result in error") 193 } 194} 195 196func TestInvalidSegments(t *testing.T) { 197 invalidSegments := []string{".", "..", "test/segment", "test%2bsegment"} 198 setters := map[string]func(string, *Request){ 199 "namespace": func(s string, r *Request) { r.Namespace(s) }, 200 "resource": func(s string, r *Request) { r.Resource(s) }, 201 "name": func(s string, r *Request) { r.Name(s) }, 202 "subresource": func(s string, r *Request) { r.SubResource(s) }, 203 } 204 for _, invalidSegment := range invalidSegments { 205 for setterName, setter := range setters { 206 r := &Request{} 207 setter(invalidSegment, r) 208 if r.err == nil { 209 t.Errorf("%s: %s: expected error, got none", setterName, invalidSegment) 210 } 211 } 212 } 213} 214 215func TestRequestParam(t *testing.T) { 216 r := (&Request{}).Param("foo", "a") 217 if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) { 218 t.Errorf("should have set a param: %#v", r) 219 } 220 221 r.Param("bar", "1") 222 r.Param("bar", "2") 223 if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}, "bar": []string{"1", "2"}}) { 224 t.Errorf("should have set a param: %#v", r) 225 } 226} 227 228func TestRequestVersionedParams(t *testing.T) { 229 r := (&Request{content: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}).Param("foo", "a") 230 if !reflect.DeepEqual(r.params, url.Values{"foo": []string{"a"}}) { 231 t.Errorf("should have set a param: %#v", r) 232 } 233 r.VersionedParams(&v1.PodLogOptions{Follow: true, Container: "bar"}, scheme.ParameterCodec) 234 235 if !reflect.DeepEqual(r.params, url.Values{ 236 "foo": []string{"a"}, 237 "container": []string{"bar"}, 238 "follow": []string{"true"}, 239 }) { 240 t.Errorf("should have set a param: %#v", r) 241 } 242} 243 244func TestRequestVersionedParamsFromListOptions(t *testing.T) { 245 r := &Request{content: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}} 246 r.VersionedParams(&metav1.ListOptions{ResourceVersion: "1"}, scheme.ParameterCodec) 247 if !reflect.DeepEqual(r.params, url.Values{ 248 "resourceVersion": []string{"1"}, 249 }) { 250 t.Errorf("should have set a param: %#v", r) 251 } 252 253 var timeout int64 = 10 254 r.VersionedParams(&metav1.ListOptions{ResourceVersion: "2", TimeoutSeconds: &timeout}, scheme.ParameterCodec) 255 if !reflect.DeepEqual(r.params, url.Values{ 256 "resourceVersion": []string{"1", "2"}, 257 "timeoutSeconds": []string{"10"}, 258 }) { 259 t.Errorf("should have set a param: %#v %v", r.params, r.err) 260 } 261} 262 263func TestRequestURI(t *testing.T) { 264 r := (&Request{}).Param("foo", "a") 265 r.Prefix("other") 266 r.RequestURI("/test?foo=b&a=b&c=1&c=2") 267 if r.pathPrefix != "/test" { 268 t.Errorf("path is wrong: %#v", r) 269 } 270 if !reflect.DeepEqual(r.params, url.Values{"a": []string{"b"}, "foo": []string{"b"}, "c": []string{"1", "2"}}) { 271 t.Errorf("should have set a param: %#v", r) 272 } 273} 274 275type NotAnAPIObject struct{} 276 277func (obj NotAnAPIObject) GroupVersionKind() *schema.GroupVersionKind { return nil } 278func (obj NotAnAPIObject) SetGroupVersionKind(gvk *schema.GroupVersionKind) {} 279 280func defaultContentConfig() ContentConfig { 281 gvCopy := v1.SchemeGroupVersion 282 return ContentConfig{ 283 ContentType: "application/json", 284 GroupVersion: &gvCopy, 285 NegotiatedSerializer: scheme.Codecs.WithoutConversion(), 286 } 287} 288 289func defaultSerializers(t *testing.T) Serializers { 290 config := defaultContentConfig() 291 serializers, err := createSerializers(config) 292 if err != nil { 293 t.Fatalf("unexpected error: %v", err) 294 } 295 return *serializers 296} 297 298func TestRequestBody(t *testing.T) { 299 // test unknown type 300 r := (&Request{}).Body([]string{"test"}) 301 if r.err == nil || r.body != nil { 302 t.Errorf("should have set err and left body nil: %#v", r) 303 } 304 305 // test error set when failing to read file 306 f, err := ioutil.TempFile("", "test") 307 if err != nil { 308 t.Fatalf("unable to create temp file") 309 } 310 defer f.Close() 311 os.Remove(f.Name()) 312 r = (&Request{}).Body(f.Name()) 313 if r.err == nil || r.body != nil { 314 t.Errorf("should have set err and left body nil: %#v", r) 315 } 316 317 // test unencodable api object 318 r = (&Request{content: defaultContentConfig()}).Body(&NotAnAPIObject{}) 319 if r.err == nil || r.body != nil { 320 t.Errorf("should have set err and left body nil: %#v", r) 321 } 322} 323 324func TestResultIntoWithErrReturnsErr(t *testing.T) { 325 res := Result{err: errors.New("test")} 326 if err := res.Into(&v1.Pod{}); err != res.err { 327 t.Errorf("should have returned exact error from result") 328 } 329} 330 331func TestResultIntoWithNoBodyReturnsErr(t *testing.T) { 332 res := Result{ 333 body: []byte{}, 334 decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 335 } 336 if err := res.Into(&v1.Pod{}); err == nil || !strings.Contains(err.Error(), "0-length") { 337 t.Errorf("should have complained about 0 length body") 338 } 339} 340 341func TestURLTemplate(t *testing.T) { 342 uri, _ := url.Parse("http://localhost/some/base/url/path") 343 testCases := []struct { 344 Request *Request 345 ExpectedFullURL string 346 ExpectedFinalURL string 347 }{ 348 { 349 // non dynamic client 350 Request: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 351 Prefix("api", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"), 352 ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/nm?p0=v0", 353 ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D?p0=%7Bvalue%7D", 354 }, 355 { 356 // non dynamic client with wrong api group 357 Request: NewRequest(nil, "POST", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 358 Prefix("pre1", "v1").Resource("r1").Namespace("ns").Name("nm").Param("p0", "v0"), 359 ExpectedFullURL: "http://localhost/some/base/url/path/pre1/v1/namespaces/ns/r1/nm?p0=v0", 360 ExpectedFinalURL: "http://localhost/%7Bprefix%7D", 361 }, 362 { 363 // dynamic client with core group + namespace + resourceResource (with name) 364 // /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 365 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 366 Prefix("/api/v1/namespaces/ns/r1/name1"), 367 ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1/name1", 368 ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D", 369 }, 370 { 371 // dynamic client with named group + namespace + resourceResource (with name) 372 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 373 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 374 Prefix("/apis/g1/v1/namespaces/ns/r1/name1"), 375 ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1/name1", 376 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1/%7Bname%7D", 377 }, 378 { 379 // dynamic client with core group + namespace + resourceResource (with NO name) 380 // /api/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE 381 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 382 Prefix("/api/v1/namespaces/ns/r1"), 383 ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/namespaces/ns/r1", 384 ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/namespaces/%7Bnamespace%7D/r1", 385 }, 386 { 387 // dynamic client with named group + namespace + resourceResource (with NO name) 388 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE 389 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 390 Prefix("/apis/g1/v1/namespaces/ns/r1"), 391 ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/ns/r1", 392 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/namespaces/%7Bnamespace%7D/r1", 393 }, 394 { 395 // dynamic client with core group + resourceResource (with name) 396 // /api/$RESOURCEVERSION/$RESOURCE/%NAME 397 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 398 Prefix("/api/v1/r1/name1"), 399 ExpectedFullURL: "http://localhost/some/base/url/path/api/v1/r1/name1", 400 ExpectedFinalURL: "http://localhost/some/base/url/path/api/v1/r1/%7Bname%7D", 401 }, 402 { 403 // dynamic client with named group + resourceResource (with name) 404 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME 405 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 406 Prefix("/apis/g1/v1/r1/name1"), 407 ExpectedFullURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/name1", 408 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/g1/v1/r1/%7Bname%7D", 409 }, 410 { 411 // dynamic client with named group + namespace + resourceResource (with name) + subresource 412 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE 413 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 414 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"), 415 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize", 416 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D/finalize", 417 }, 418 { 419 // dynamic client with named group + namespace + resourceResource (with name) 420 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 421 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 422 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces"), 423 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces", 424 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/%7Bname%7D", 425 }, 426 { 427 // dynamic client with named group + namespace + resourceResource (with NO name) + subresource 428 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE 429 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 430 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"), 431 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/finalize", 432 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/finalize", 433 }, 434 { 435 // dynamic client with named group + namespace + resourceResource (with NO name) + subresource 436 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%SUBRESOURCE 437 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 438 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status"), 439 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces/status", 440 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces/status", 441 }, 442 { 443 // dynamic client with named group + namespace + resourceResource (with no name) 444 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 445 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 446 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/namespaces"), 447 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/namespaces", 448 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bnamespace%7D/namespaces", 449 }, 450 { 451 // dynamic client with named group + resourceResource (with name) + subresource 452 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 453 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 454 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/finalize"), 455 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/finalize", 456 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/finalize", 457 }, 458 { 459 // dynamic client with named group + resourceResource (with name) + subresource 460 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME 461 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 462 Prefix("/apis/namespaces/namespaces/namespaces/namespaces/status"), 463 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces/status", 464 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D/status", 465 }, 466 { 467 // dynamic client with named group + resourceResource (with name) 468 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME 469 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 470 Prefix("/apis/namespaces/namespaces/namespaces/namespaces"), 471 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/namespaces", 472 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces/%7Bname%7D", 473 }, 474 { 475 // dynamic client with named group + resourceResource (with no name) 476 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/$RESOURCE/%NAME 477 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 478 Prefix("/apis/namespaces/namespaces/namespaces"), 479 ExpectedFullURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces", 480 ExpectedFinalURL: "http://localhost/some/base/url/path/apis/namespaces/namespaces/namespaces", 481 }, 482 { 483 // dynamic client with wrong api group + namespace + resourceResource (with name) + subresource 484 // /apis/$NAMEDGROUPNAME/$RESOURCEVERSION/namespaces/$NAMESPACE/$RESOURCE/%NAME/$SUBRESOURCE 485 Request: NewRequest(nil, "DELETE", uri, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0). 486 Prefix("/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize"), 487 ExpectedFullURL: "http://localhost/some/base/url/path/pre1/namespaces/namespaces/namespaces/namespaces/namespaces/namespaces/finalize", 488 ExpectedFinalURL: "http://localhost/%7Bprefix%7D", 489 }, 490 } 491 for i, testCase := range testCases { 492 r := testCase.Request 493 full := r.URL() 494 if full.String() != testCase.ExpectedFullURL { 495 t.Errorf("%d: unexpected initial URL: %s %s", i, full, testCase.ExpectedFullURL) 496 } 497 actualURL := r.finalURLTemplate() 498 actual := actualURL.String() 499 if actual != testCase.ExpectedFinalURL { 500 t.Errorf("%d: unexpected URL template: %s %s", i, actual, testCase.ExpectedFinalURL) 501 } 502 if r.URL().String() != full.String() { 503 t.Errorf("%d, creating URL template changed request: %s -> %s", i, full.String(), r.URL().String()) 504 } 505 } 506} 507 508func TestTransformResponse(t *testing.T) { 509 invalid := []byte("aaaaa") 510 uri, _ := url.Parse("http://localhost") 511 testCases := []struct { 512 Response *http.Response 513 Data []byte 514 Created bool 515 Error bool 516 ErrFn func(err error) bool 517 }{ 518 {Response: &http.Response{StatusCode: 200}, Data: []byte{}}, 519 {Response: &http.Response{StatusCode: 201}, Data: []byte{}, Created: true}, 520 {Response: &http.Response{StatusCode: 199}, Error: true}, 521 {Response: &http.Response{StatusCode: 500}, Error: true}, 522 {Response: &http.Response{StatusCode: 422}, Error: true}, 523 {Response: &http.Response{StatusCode: 409}, Error: true}, 524 {Response: &http.Response{StatusCode: 404}, Error: true}, 525 {Response: &http.Response{StatusCode: 401}, Error: true}, 526 { 527 Response: &http.Response{ 528 StatusCode: 401, 529 Header: http.Header{"Content-Type": []string{"application/json"}}, 530 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 531 }, 532 Error: true, 533 ErrFn: func(err error) bool { 534 return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) 535 }, 536 }, 537 { 538 Response: &http.Response{ 539 StatusCode: 401, 540 Header: http.Header{"Content-Type": []string{"text/any"}}, 541 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 542 }, 543 Error: true, 544 ErrFn: func(err error) bool { 545 return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err) 546 }, 547 }, 548 {Response: &http.Response{StatusCode: 403}, Error: true}, 549 {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, 550 {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, 551 } 552 for i, test := range testCases { 553 r := NewRequest(nil, "", uri, "", defaultContentConfig(), defaultSerializers(t), nil, nil, 0) 554 if test.Response.Body == nil { 555 test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) 556 } 557 result := r.transformResponse(test.Response, &http.Request{}) 558 response, created, err := result.body, result.statusCode == http.StatusCreated, result.err 559 hasErr := err != nil 560 if hasErr != test.Error { 561 t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) 562 } else if hasErr && test.Response.StatusCode > 399 { 563 status, ok := err.(apierrors.APIStatus) 564 if !ok { 565 t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) 566 continue 567 } 568 if int(status.Status().Code) != test.Response.StatusCode { 569 t.Errorf("%d: status code did not match response: %#v", i, status.Status()) 570 } 571 } 572 if test.ErrFn != nil && !test.ErrFn(err) { 573 t.Errorf("%d: error function did not match: %v", i, err) 574 } 575 if !(test.Data == nil && response == nil) && !apiequality.Semantic.DeepDerivative(test.Data, response) { 576 t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response) 577 } 578 if test.Created != created { 579 t.Errorf("%d: expected created %t, got %t", i, test.Created, created) 580 } 581 } 582} 583 584type renegotiator struct { 585 called bool 586 contentType string 587 params map[string]string 588 decoder runtime.Decoder 589 err error 590} 591 592func (r *renegotiator) invoke(contentType string, params map[string]string) (runtime.Decoder, error) { 593 r.called = true 594 r.contentType = contentType 595 r.params = params 596 return r.decoder, r.err 597} 598 599func TestTransformResponseNegotiate(t *testing.T) { 600 invalid := []byte("aaaaa") 601 uri, _ := url.Parse("http://localhost") 602 testCases := []struct { 603 Response *http.Response 604 Data []byte 605 Created bool 606 Error bool 607 ErrFn func(err error) bool 608 609 ContentType string 610 Called bool 611 ExpectContentType string 612 Decoder runtime.Decoder 613 NegotiateErr error 614 }{ 615 { 616 ContentType: "application/json", 617 Response: &http.Response{ 618 StatusCode: 401, 619 Header: http.Header{"Content-Type": []string{"application/json"}}, 620 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 621 }, 622 Error: true, 623 ErrFn: func(err error) bool { 624 return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) 625 }, 626 }, 627 { 628 ContentType: "application/json", 629 Response: &http.Response{ 630 StatusCode: 401, 631 Header: http.Header{"Content-Type": []string{"application/protobuf"}}, 632 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 633 }, 634 Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 635 636 Called: true, 637 ExpectContentType: "application/protobuf", 638 639 Error: true, 640 ErrFn: func(err error) bool { 641 return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) 642 }, 643 }, 644 { 645 ContentType: "application/json", 646 Response: &http.Response{ 647 StatusCode: 500, 648 Header: http.Header{"Content-Type": []string{"application/,others"}}, 649 }, 650 Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 651 652 Error: true, 653 ErrFn: func(err error) bool { 654 return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500 655 }, 656 }, 657 { 658 // no negotiation when no content type specified 659 Response: &http.Response{ 660 StatusCode: 200, 661 Header: http.Header{"Content-Type": []string{"text/any"}}, 662 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 663 }, 664 Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 665 }, 666 { 667 // no negotiation when no response content type specified 668 ContentType: "text/any", 669 Response: &http.Response{ 670 StatusCode: 200, 671 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 672 }, 673 Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 674 }, 675 { 676 // unrecognized content type is not handled 677 ContentType: "application/json", 678 Response: &http.Response{ 679 StatusCode: 404, 680 Header: http.Header{"Content-Type": []string{"application/unrecognized"}}, 681 Body: ioutil.NopCloser(bytes.NewReader(invalid)), 682 }, 683 Decoder: scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), 684 685 NegotiateErr: fmt.Errorf("aaaa"), 686 Called: true, 687 ExpectContentType: "application/unrecognized", 688 689 Error: true, 690 ErrFn: func(err error) bool { 691 return err.Error() != "aaaaa" && apierrors.IsNotFound(err) 692 }, 693 }, 694 } 695 for i, test := range testCases { 696 serializers := defaultSerializers(t) 697 negotiator := &renegotiator{ 698 decoder: test.Decoder, 699 err: test.NegotiateErr, 700 } 701 serializers.RenegotiatedDecoder = negotiator.invoke 702 contentConfig := defaultContentConfig() 703 contentConfig.ContentType = test.ContentType 704 r := NewRequest(nil, "", uri, "", contentConfig, serializers, nil, nil, 0) 705 if test.Response.Body == nil { 706 test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) 707 } 708 result := r.transformResponse(test.Response, &http.Request{}) 709 _, err := result.body, result.err 710 hasErr := err != nil 711 if hasErr != test.Error { 712 t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) 713 continue 714 } else if hasErr && test.Response.StatusCode > 399 { 715 status, ok := err.(apierrors.APIStatus) 716 if !ok { 717 t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) 718 continue 719 } 720 if int(status.Status().Code) != test.Response.StatusCode { 721 t.Errorf("%d: status code did not match response: %#v", i, status.Status()) 722 } 723 } 724 if test.ErrFn != nil && !test.ErrFn(err) { 725 t.Errorf("%d: error function did not match: %v", i, err) 726 } 727 if negotiator.called != test.Called { 728 t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called) 729 } 730 if !test.Called { 731 continue 732 } 733 if negotiator.contentType != test.ExpectContentType { 734 t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType) 735 } 736 } 737} 738 739func TestTransformUnstructuredError(t *testing.T) { 740 testCases := []struct { 741 Req *http.Request 742 Res *http.Response 743 744 Resource string 745 Name string 746 747 ErrFn func(error) bool 748 Transformed error 749 }{ 750 { 751 Resource: "foo", 752 Name: "bar", 753 Req: &http.Request{ 754 Method: "POST", 755 }, 756 Res: &http.Response{ 757 StatusCode: http.StatusConflict, 758 Body: ioutil.NopCloser(bytes.NewReader(nil)), 759 }, 760 ErrFn: apierrors.IsAlreadyExists, 761 }, 762 { 763 Resource: "foo", 764 Name: "bar", 765 Req: &http.Request{ 766 Method: "PUT", 767 }, 768 Res: &http.Response{ 769 StatusCode: http.StatusConflict, 770 Body: ioutil.NopCloser(bytes.NewReader(nil)), 771 }, 772 ErrFn: apierrors.IsConflict, 773 }, 774 { 775 Resource: "foo", 776 Name: "bar", 777 Req: &http.Request{}, 778 Res: &http.Response{ 779 StatusCode: http.StatusNotFound, 780 Body: ioutil.NopCloser(bytes.NewReader(nil)), 781 }, 782 ErrFn: apierrors.IsNotFound, 783 }, 784 { 785 Req: &http.Request{}, 786 Res: &http.Response{ 787 StatusCode: http.StatusBadRequest, 788 Body: ioutil.NopCloser(bytes.NewReader(nil)), 789 }, 790 ErrFn: apierrors.IsBadRequest, 791 }, 792 { 793 // status in response overrides transformed result 794 Req: &http.Request{}, 795 Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))}, 796 ErrFn: apierrors.IsBadRequest, 797 Transformed: &apierrors.StatusError{ 798 ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound}, 799 }, 800 }, 801 { 802 // successful status is ignored 803 Req: &http.Request{}, 804 Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))}, 805 ErrFn: apierrors.IsBadRequest, 806 }, 807 { 808 // empty object does not change result 809 Req: &http.Request{}, 810 Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))}, 811 ErrFn: apierrors.IsBadRequest, 812 }, 813 { 814 // we default apiVersion for backwards compatibility with old clients 815 // TODO: potentially remove in 1.7 816 Req: &http.Request{}, 817 Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))}, 818 ErrFn: apierrors.IsBadRequest, 819 Transformed: &apierrors.StatusError{ 820 ErrStatus: metav1.Status{Status: metav1.StatusFailure, Code: http.StatusNotFound}, 821 }, 822 }, 823 { 824 // we do not default kind 825 Req: &http.Request{}, 826 Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))}, 827 ErrFn: apierrors.IsBadRequest, 828 }, 829 } 830 831 for i, testCase := range testCases { 832 r := &Request{ 833 content: defaultContentConfig(), 834 serializers: defaultSerializers(t), 835 resourceName: testCase.Name, 836 resource: testCase.Resource, 837 } 838 result := r.transformResponse(testCase.Res, testCase.Req) 839 err := result.err 840 if !testCase.ErrFn(err) { 841 t.Errorf("unexpected error: %v", err) 842 continue 843 } 844 if !apierrors.IsUnexpectedServerError(err) { 845 t.Errorf("%d: unexpected error type: %v", i, err) 846 } 847 if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) { 848 t.Errorf("unexpected error string: %s", err) 849 } 850 if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) { 851 t.Errorf("unexpected error string: %s", err) 852 } 853 854 // verify Error() properly transforms the error 855 transformed := result.Error() 856 expect := testCase.Transformed 857 if expect == nil { 858 expect = err 859 } 860 if !reflect.DeepEqual(expect, transformed) { 861 t.Errorf("%d: unexpected Error(): %s", i, diff.ObjectReflectDiff(expect, transformed)) 862 } 863 864 // verify result.Get properly transforms the error 865 if _, err := result.Get(); !reflect.DeepEqual(expect, err) { 866 t.Errorf("%d: unexpected error on Get(): %s", i, diff.ObjectReflectDiff(expect, err)) 867 } 868 869 // verify result.Into properly handles the error 870 if err := result.Into(&v1.Pod{}); !reflect.DeepEqual(expect, err) { 871 t.Errorf("%d: unexpected error on Into(): %s", i, diff.ObjectReflectDiff(expect, err)) 872 } 873 874 // verify result.Raw leaves the error in the untransformed state 875 if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) { 876 t.Errorf("%d: unexpected error on Raw(): %s", i, diff.ObjectReflectDiff(expect, err)) 877 } 878 } 879} 880 881type errorReader struct { 882 err error 883} 884 885func (r errorReader) Read(data []byte) (int, error) { return 0, r.err } 886func (r errorReader) Close() error { return nil } 887 888func TestRequestWatch(t *testing.T) { 889 testCases := []struct { 890 Request *Request 891 Expect []watch.Event 892 Err bool 893 ErrFn func(error) bool 894 Empty bool 895 }{ 896 { 897 Request: &Request{err: errors.New("bail")}, 898 Err: true, 899 }, 900 { 901 Request: &Request{baseURL: &url.URL{}, pathPrefix: "%"}, 902 Err: true, 903 }, 904 { 905 Request: &Request{ 906 client: clientFunc(func(req *http.Request) (*http.Response, error) { 907 return nil, errors.New("err") 908 }), 909 baseURL: &url.URL{}, 910 }, 911 Err: true, 912 }, 913 { 914 Request: &Request{ 915 content: defaultContentConfig(), 916 serializers: defaultSerializers(t), 917 client: clientFunc(func(req *http.Request) (*http.Response, error) { 918 resp := &http.Response{StatusCode: http.StatusOK, Body: errorReader{err: errors.New("test error")}} 919 return resp, nil 920 }), 921 baseURL: &url.URL{}, 922 }, 923 Expect: []watch.Event{ 924 { 925 Type: watch.Error, 926 Object: &metav1.Status{ 927 Status: "Failure", 928 Code: 500, 929 Reason: "InternalError", 930 Message: `an error on the server ("unable to decode an event from the watch stream: test error") has prevented the request from succeeding`, 931 Details: &metav1.StatusDetails{ 932 Causes: []metav1.StatusCause{ 933 { 934 Type: "UnexpectedServerResponse", 935 Message: "unable to decode an event from the watch stream: test error", 936 }, 937 { 938 Type: "ClientWatchDecoding", 939 Message: "unable to decode an event from the watch stream: test error", 940 }, 941 }, 942 }, 943 }, 944 }, 945 }, 946 }, 947 { 948 Request: &Request{ 949 content: defaultContentConfig(), 950 serializers: defaultSerializers(t), 951 client: clientFunc(func(req *http.Request) (*http.Response, error) { 952 return &http.Response{ 953 StatusCode: http.StatusForbidden, 954 Body: ioutil.NopCloser(bytes.NewReader([]byte{})), 955 }, nil 956 }), 957 baseURL: &url.URL{}, 958 }, 959 Err: true, 960 ErrFn: func(err error) bool { 961 return apierrors.IsForbidden(err) 962 }, 963 }, 964 { 965 Request: &Request{ 966 content: defaultContentConfig(), 967 serializers: defaultSerializers(t), 968 client: clientFunc(func(req *http.Request) (*http.Response, error) { 969 return &http.Response{ 970 StatusCode: http.StatusUnauthorized, 971 Body: ioutil.NopCloser(bytes.NewReader([]byte{})), 972 }, nil 973 }), 974 baseURL: &url.URL{}, 975 }, 976 Err: true, 977 ErrFn: func(err error) bool { 978 return apierrors.IsUnauthorized(err) 979 }, 980 }, 981 { 982 Request: &Request{ 983 content: defaultContentConfig(), 984 serializers: defaultSerializers(t), 985 client: clientFunc(func(req *http.Request) (*http.Response, error) { 986 return &http.Response{ 987 StatusCode: http.StatusUnauthorized, 988 Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{ 989 Status: metav1.StatusFailure, 990 Reason: metav1.StatusReasonUnauthorized, 991 })))), 992 }, nil 993 }), 994 baseURL: &url.URL{}, 995 }, 996 Err: true, 997 ErrFn: func(err error) bool { 998 return apierrors.IsUnauthorized(err) 999 }, 1000 }, 1001 { 1002 Request: &Request{ 1003 serializers: defaultSerializers(t), 1004 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1005 return nil, io.EOF 1006 }), 1007 baseURL: &url.URL{}, 1008 }, 1009 Empty: true, 1010 }, 1011 { 1012 Request: &Request{ 1013 serializers: defaultSerializers(t), 1014 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1015 return nil, &url.Error{Err: io.EOF} 1016 }), 1017 baseURL: &url.URL{}, 1018 }, 1019 Empty: true, 1020 }, 1021 { 1022 Request: &Request{ 1023 serializers: defaultSerializers(t), 1024 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1025 return nil, errors.New("http: can't write HTTP request on broken connection") 1026 }), 1027 baseURL: &url.URL{}, 1028 }, 1029 Empty: true, 1030 }, 1031 { 1032 Request: &Request{ 1033 serializers: defaultSerializers(t), 1034 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1035 return nil, errors.New("foo: connection reset by peer") 1036 }), 1037 baseURL: &url.URL{}, 1038 }, 1039 Empty: true, 1040 }, 1041 } 1042 for i, testCase := range testCases { 1043 t.Run("", func(t *testing.T) { 1044 testCase.Request.backoffMgr = &NoBackoff{} 1045 watch, err := testCase.Request.Watch() 1046 hasErr := err != nil 1047 if hasErr != testCase.Err { 1048 t.Fatalf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err) 1049 } 1050 if testCase.ErrFn != nil && !testCase.ErrFn(err) { 1051 t.Errorf("%d: error not valid: %v", i, err) 1052 } 1053 if hasErr && watch != nil { 1054 t.Fatalf("%d: watch should be nil when error is returned", i) 1055 } 1056 if testCase.Empty { 1057 _, ok := <-watch.ResultChan() 1058 if ok { 1059 t.Errorf("%d: expected the watch to be empty: %#v", i, watch) 1060 } 1061 } 1062 if testCase.Expect != nil { 1063 for i, evt := range testCase.Expect { 1064 out, ok := <-watch.ResultChan() 1065 if !ok { 1066 t.Fatalf("Watch closed early, %d/%d read", i, len(testCase.Expect)) 1067 } 1068 if !reflect.DeepEqual(evt, out) { 1069 t.Fatalf("Event %d does not match: %s", i, diff.ObjectReflectDiff(evt, out)) 1070 } 1071 } 1072 } 1073 }) 1074 } 1075} 1076 1077func TestRequestStream(t *testing.T) { 1078 testCases := []struct { 1079 Request *Request 1080 Err bool 1081 ErrFn func(error) bool 1082 }{ 1083 { 1084 Request: &Request{err: errors.New("bail")}, 1085 Err: true, 1086 }, 1087 { 1088 Request: &Request{baseURL: &url.URL{}, pathPrefix: "%"}, 1089 Err: true, 1090 }, 1091 { 1092 Request: &Request{ 1093 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1094 return nil, errors.New("err") 1095 }), 1096 baseURL: &url.URL{}, 1097 }, 1098 Err: true, 1099 }, 1100 { 1101 Request: &Request{ 1102 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1103 return &http.Response{ 1104 StatusCode: http.StatusUnauthorized, 1105 Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &metav1.Status{ 1106 Status: metav1.StatusFailure, 1107 Reason: metav1.StatusReasonUnauthorized, 1108 })))), 1109 }, nil 1110 }), 1111 content: defaultContentConfig(), 1112 serializers: defaultSerializers(t), 1113 baseURL: &url.URL{}, 1114 }, 1115 Err: true, 1116 }, 1117 { 1118 Request: &Request{ 1119 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1120 return &http.Response{ 1121 StatusCode: http.StatusBadRequest, 1122 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}`))), 1123 }, nil 1124 }), 1125 content: defaultContentConfig(), 1126 serializers: defaultSerializers(t), 1127 baseURL: &url.URL{}, 1128 }, 1129 Err: true, 1130 ErrFn: func(err error) bool { 1131 if err.Error() == "a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]" { 1132 return true 1133 } 1134 return false 1135 }, 1136 }, 1137 } 1138 for i, testCase := range testCases { 1139 testCase.Request.backoffMgr = &NoBackoff{} 1140 body, err := testCase.Request.Stream() 1141 hasErr := err != nil 1142 if hasErr != testCase.Err { 1143 t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err) 1144 } 1145 if hasErr && body != nil { 1146 t.Errorf("%d: body should be nil when error is returned", i) 1147 } 1148 1149 if hasErr { 1150 if testCase.ErrFn != nil && !testCase.ErrFn(err) { 1151 t.Errorf("unexpected error: %v", err) 1152 } 1153 } 1154 } 1155} 1156 1157type fakeUpgradeConnection struct{} 1158 1159func (c *fakeUpgradeConnection) CreateStream(headers http.Header) (httpstream.Stream, error) { 1160 return nil, nil 1161} 1162func (c *fakeUpgradeConnection) Close() error { 1163 return nil 1164} 1165func (c *fakeUpgradeConnection) CloseChan() <-chan bool { 1166 return make(chan bool) 1167} 1168func (c *fakeUpgradeConnection) SetIdleTimeout(timeout time.Duration) { 1169} 1170 1171type fakeUpgradeRoundTripper struct { 1172 req *http.Request 1173 conn httpstream.Connection 1174} 1175 1176func (f *fakeUpgradeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 1177 f.req = req 1178 b := []byte{} 1179 body := ioutil.NopCloser(bytes.NewReader(b)) 1180 resp := &http.Response{ 1181 StatusCode: 101, 1182 Body: body, 1183 } 1184 return resp, nil 1185} 1186 1187func (f *fakeUpgradeRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) { 1188 return f.conn, nil 1189} 1190 1191func TestRequestDo(t *testing.T) { 1192 testCases := []struct { 1193 Request *Request 1194 Err bool 1195 }{ 1196 { 1197 Request: &Request{err: errors.New("bail")}, 1198 Err: true, 1199 }, 1200 { 1201 Request: &Request{baseURL: &url.URL{}, pathPrefix: "%"}, 1202 Err: true, 1203 }, 1204 { 1205 Request: &Request{ 1206 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1207 return nil, errors.New("err") 1208 }), 1209 baseURL: &url.URL{}, 1210 }, 1211 Err: true, 1212 }, 1213 } 1214 for i, testCase := range testCases { 1215 testCase.Request.backoffMgr = &NoBackoff{} 1216 body, err := testCase.Request.Do().Raw() 1217 hasErr := err != nil 1218 if hasErr != testCase.Err { 1219 t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err) 1220 } 1221 if hasErr && body != nil { 1222 t.Errorf("%d: body should be nil when error is returned", i) 1223 } 1224 } 1225} 1226 1227func TestDoRequestNewWay(t *testing.T) { 1228 reqBody := "request body" 1229 expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{ 1230 Protocol: "TCP", 1231 Port: 12345, 1232 TargetPort: intstr.FromInt(12345), 1233 }}}} 1234 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj) 1235 fakeHandler := utiltesting.FakeHandler{ 1236 StatusCode: 200, 1237 ResponseBody: string(expectedBody), 1238 T: t, 1239 } 1240 testServer := httptest.NewServer(&fakeHandler) 1241 defer testServer.Close() 1242 c := testRESTClient(t, testServer) 1243 obj, err := c.Verb("POST"). 1244 Prefix("foo", "bar"). 1245 Suffix("baz"). 1246 Timeout(time.Second). 1247 Body([]byte(reqBody)). 1248 Do().Get() 1249 if err != nil { 1250 t.Errorf("Unexpected error: %v %#v", err, err) 1251 return 1252 } 1253 if obj == nil { 1254 t.Error("nil obj") 1255 } else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) { 1256 t.Errorf("Expected: %#v, got %#v", expectedObj, obj) 1257 } 1258 requestURL := defaultResourcePathWithPrefix("foo/bar", "", "", "baz") 1259 requestURL += "?timeout=1s" 1260 fakeHandler.ValidateRequest(t, requestURL, "POST", &reqBody) 1261} 1262 1263// This test assumes that the client implementation backs off exponentially, for an individual request. 1264func TestBackoffLifecycle(t *testing.T) { 1265 count := 0 1266 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1267 count++ 1268 t.Logf("Attempt %d", count) 1269 if count == 5 || count == 9 { 1270 w.WriteHeader(http.StatusOK) 1271 return 1272 } 1273 w.WriteHeader(http.StatusGatewayTimeout) 1274 return 1275 })) 1276 defer testServer.Close() 1277 c := testRESTClient(t, testServer) 1278 1279 // Test backoff recovery and increase. This correlates to the constants 1280 // which are used in the server implementation returning StatusOK above. 1281 seconds := []int{0, 1, 2, 4, 8, 0, 1, 2, 4, 0} 1282 request := c.Verb("POST").Prefix("backofftest").Suffix("abc") 1283 clock := clock.FakeClock{} 1284 request.backoffMgr = &URLBackoff{ 1285 // Use a fake backoff here to avoid flakes and speed the test up. 1286 Backoff: flowcontrol.NewFakeBackOff( 1287 time.Duration(1)*time.Second, 1288 time.Duration(200)*time.Second, 1289 &clock, 1290 )} 1291 1292 for _, sec := range seconds { 1293 thisBackoff := request.backoffMgr.CalculateBackoff(request.URL()) 1294 t.Logf("Current backoff %v", thisBackoff) 1295 if thisBackoff != time.Duration(sec)*time.Second { 1296 t.Errorf("Backoff is %v instead of %v", thisBackoff, sec) 1297 } 1298 now := clock.Now() 1299 request.DoRaw() 1300 elapsed := clock.Since(now) 1301 if clock.Since(now) != thisBackoff { 1302 t.Errorf("CalculatedBackoff not honored by clock: Expected time of %v, but got %v ", thisBackoff, elapsed) 1303 } 1304 } 1305} 1306 1307type testBackoffManager struct { 1308 sleeps []time.Duration 1309} 1310 1311func (b *testBackoffManager) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) { 1312} 1313 1314func (b *testBackoffManager) CalculateBackoff(actualUrl *url.URL) time.Duration { 1315 return time.Duration(0) 1316} 1317 1318func (b *testBackoffManager) Sleep(d time.Duration) { 1319 b.sleeps = append(b.sleeps, d) 1320} 1321 1322func TestCheckRetryClosesBody(t *testing.T) { 1323 count := 0 1324 ch := make(chan struct{}) 1325 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1326 count++ 1327 t.Logf("attempt %d", count) 1328 if count >= 5 { 1329 w.WriteHeader(http.StatusOK) 1330 close(ch) 1331 return 1332 } 1333 w.Header().Set("Retry-After", "1") 1334 http.Error(w, "Too many requests, please try again later.", http.StatusTooManyRequests) 1335 })) 1336 defer testServer.Close() 1337 1338 backoffMgr := &testBackoffManager{} 1339 expectedSleeps := []time.Duration{0, time.Second, 0, time.Second, 0, time.Second, 0, time.Second, 0} 1340 1341 c := testRESTClient(t, testServer) 1342 c.createBackoffMgr = func() BackoffManager { return backoffMgr } 1343 _, err := c.Verb("POST"). 1344 Prefix("foo", "bar"). 1345 Suffix("baz"). 1346 Timeout(time.Second). 1347 Body([]byte(strings.Repeat("abcd", 1000))). 1348 DoRaw() 1349 if err != nil { 1350 t.Fatalf("Unexpected error: %v %#v", err, err) 1351 } 1352 <-ch 1353 if count != 5 { 1354 t.Errorf("unexpected retries: %d", count) 1355 } 1356 if !reflect.DeepEqual(backoffMgr.sleeps, expectedSleeps) { 1357 t.Errorf("unexpected sleeps, expected: %v, got: %v", expectedSleeps, backoffMgr.sleeps) 1358 } 1359} 1360 1361func TestConnectionResetByPeerIsRetried(t *testing.T) { 1362 count := 0 1363 backoff := &testBackoffManager{} 1364 req := &Request{ 1365 verb: "GET", 1366 client: clientFunc(func(req *http.Request) (*http.Response, error) { 1367 count++ 1368 if count >= 3 { 1369 return &http.Response{ 1370 StatusCode: 200, 1371 Body: ioutil.NopCloser(bytes.NewReader([]byte{})), 1372 }, nil 1373 } 1374 return nil, &net.OpError{Err: syscall.ECONNRESET} 1375 }), 1376 backoffMgr: backoff, 1377 } 1378 // We expect two retries of "connection reset by peer" and the success. 1379 _, err := req.Do().Raw() 1380 if err != nil { 1381 t.Errorf("Unexpected error: %v", err) 1382 } 1383 // We have a sleep before each retry (including the initial one) and for 1384 // every "retry-after" call - thus 5 together. 1385 if len(backoff.sleeps) != 5 { 1386 t.Errorf("Expected 5 retries, got: %d", len(backoff.sleeps)) 1387 } 1388} 1389 1390func TestCheckRetryHandles429And5xx(t *testing.T) { 1391 count := 0 1392 ch := make(chan struct{}) 1393 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1394 data, err := ioutil.ReadAll(req.Body) 1395 if err != nil { 1396 t.Fatalf("unable to read request body: %v", err) 1397 } 1398 if !bytes.Equal(data, []byte(strings.Repeat("abcd", 1000))) { 1399 t.Fatalf("retry did not send a complete body: %s", data) 1400 } 1401 t.Logf("attempt %d", count) 1402 if count >= 4 { 1403 w.WriteHeader(http.StatusOK) 1404 close(ch) 1405 return 1406 } 1407 w.Header().Set("Retry-After", "0") 1408 w.WriteHeader([]int{http.StatusTooManyRequests, 500, 501, 504}[count]) 1409 count++ 1410 })) 1411 defer testServer.Close() 1412 1413 c := testRESTClient(t, testServer) 1414 _, err := c.Verb("POST"). 1415 Prefix("foo", "bar"). 1416 Suffix("baz"). 1417 Timeout(time.Second). 1418 Body([]byte(strings.Repeat("abcd", 1000))). 1419 DoRaw() 1420 if err != nil { 1421 t.Fatalf("Unexpected error: %v %#v", err, err) 1422 } 1423 <-ch 1424 if count != 4 { 1425 t.Errorf("unexpected retries: %d", count) 1426 } 1427} 1428 1429func BenchmarkCheckRetryClosesBody(b *testing.B) { 1430 count := 0 1431 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1432 count++ 1433 if count%3 == 0 { 1434 w.WriteHeader(http.StatusOK) 1435 return 1436 } 1437 w.Header().Set("Retry-After", "0") 1438 w.WriteHeader(http.StatusTooManyRequests) 1439 })) 1440 defer testServer.Close() 1441 1442 c := testRESTClient(b, testServer) 1443 1444 requests := make([]*Request, 0, b.N) 1445 for i := 0; i < b.N; i++ { 1446 requests = append(requests, c.Verb("POST"). 1447 Prefix("foo", "bar"). 1448 Suffix("baz"). 1449 Timeout(time.Second). 1450 Body([]byte(strings.Repeat("abcd", 1000)))) 1451 } 1452 1453 b.ResetTimer() 1454 for i := 0; i < b.N; i++ { 1455 if _, err := requests[i].DoRaw(); err != nil { 1456 b.Fatalf("Unexpected error (%d/%d): %v", i, b.N, err) 1457 } 1458 } 1459} 1460 1461func TestDoRequestNewWayReader(t *testing.T) { 1462 reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 1463 reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj) 1464 expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{ 1465 Protocol: "TCP", 1466 Port: 12345, 1467 TargetPort: intstr.FromInt(12345), 1468 }}}} 1469 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj) 1470 fakeHandler := utiltesting.FakeHandler{ 1471 StatusCode: 200, 1472 ResponseBody: string(expectedBody), 1473 T: t, 1474 } 1475 testServer := httptest.NewServer(&fakeHandler) 1476 defer testServer.Close() 1477 c := testRESTClient(t, testServer) 1478 obj, err := c.Verb("POST"). 1479 Resource("bar"). 1480 Name("baz"). 1481 Prefix("foo"). 1482 Timeout(time.Second). 1483 Body(bytes.NewBuffer(reqBodyExpected)). 1484 Do().Get() 1485 if err != nil { 1486 t.Errorf("Unexpected error: %v %#v", err, err) 1487 return 1488 } 1489 if obj == nil { 1490 t.Error("nil obj") 1491 } else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) { 1492 t.Errorf("Expected: %#v, got %#v", expectedObj, obj) 1493 } 1494 tmpStr := string(reqBodyExpected) 1495 requestURL := defaultResourcePathWithPrefix("foo", "bar", "", "baz") 1496 requestURL += "?timeout=1s" 1497 fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr) 1498} 1499 1500func TestDoRequestNewWayObj(t *testing.T) { 1501 reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 1502 reqBodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj) 1503 expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{ 1504 Protocol: "TCP", 1505 Port: 12345, 1506 TargetPort: intstr.FromInt(12345), 1507 }}}} 1508 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj) 1509 fakeHandler := utiltesting.FakeHandler{ 1510 StatusCode: 200, 1511 ResponseBody: string(expectedBody), 1512 T: t, 1513 } 1514 testServer := httptest.NewServer(&fakeHandler) 1515 defer testServer.Close() 1516 c := testRESTClient(t, testServer) 1517 obj, err := c.Verb("POST"). 1518 Suffix("baz"). 1519 Name("bar"). 1520 Resource("foo"). 1521 Timeout(time.Second). 1522 Body(reqObj). 1523 Do().Get() 1524 if err != nil { 1525 t.Errorf("Unexpected error: %v %#v", err, err) 1526 return 1527 } 1528 if obj == nil { 1529 t.Error("nil obj") 1530 } else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) { 1531 t.Errorf("Expected: %#v, got %#v", expectedObj, obj) 1532 } 1533 tmpStr := string(reqBodyExpected) 1534 requestURL := defaultResourcePathWithPrefix("", "foo", "", "bar/baz") 1535 requestURL += "?timeout=1s" 1536 fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr) 1537} 1538 1539func TestDoRequestNewWayFile(t *testing.T) { 1540 reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 1541 reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj) 1542 if err != nil { 1543 t.Errorf("unexpected error: %v", err) 1544 } 1545 1546 file, err := ioutil.TempFile("", "foo") 1547 if err != nil { 1548 t.Errorf("unexpected error: %v", err) 1549 } 1550 defer file.Close() 1551 defer os.Remove(file.Name()) 1552 1553 _, err = file.Write(reqBodyExpected) 1554 if err != nil { 1555 t.Errorf("unexpected error: %v", err) 1556 } 1557 1558 expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{ 1559 Protocol: "TCP", 1560 Port: 12345, 1561 TargetPort: intstr.FromInt(12345), 1562 }}}} 1563 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj) 1564 fakeHandler := utiltesting.FakeHandler{ 1565 StatusCode: 200, 1566 ResponseBody: string(expectedBody), 1567 T: t, 1568 } 1569 testServer := httptest.NewServer(&fakeHandler) 1570 defer testServer.Close() 1571 c := testRESTClient(t, testServer) 1572 wasCreated := true 1573 obj, err := c.Verb("POST"). 1574 Prefix("foo/bar", "baz"). 1575 Timeout(time.Second). 1576 Body(file.Name()). 1577 Do().WasCreated(&wasCreated).Get() 1578 if err != nil { 1579 t.Errorf("Unexpected error: %v %#v", err, err) 1580 return 1581 } 1582 if obj == nil { 1583 t.Error("nil obj") 1584 } else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) { 1585 t.Errorf("Expected: %#v, got %#v", expectedObj, obj) 1586 } 1587 if wasCreated { 1588 t.Errorf("expected object was created") 1589 } 1590 tmpStr := string(reqBodyExpected) 1591 requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "") 1592 requestURL += "?timeout=1s" 1593 fakeHandler.ValidateRequest(t, requestURL, "POST", &tmpStr) 1594} 1595 1596func TestWasCreated(t *testing.T) { 1597 reqObj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 1598 reqBodyExpected, err := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), reqObj) 1599 if err != nil { 1600 t.Errorf("unexpected error: %v", err) 1601 } 1602 1603 expectedObj := &v1.Service{Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{ 1604 Protocol: "TCP", 1605 Port: 12345, 1606 TargetPort: intstr.FromInt(12345), 1607 }}}} 1608 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), expectedObj) 1609 fakeHandler := utiltesting.FakeHandler{ 1610 StatusCode: 201, 1611 ResponseBody: string(expectedBody), 1612 T: t, 1613 } 1614 testServer := httptest.NewServer(&fakeHandler) 1615 defer testServer.Close() 1616 c := testRESTClient(t, testServer) 1617 wasCreated := false 1618 obj, err := c.Verb("PUT"). 1619 Prefix("foo/bar", "baz"). 1620 Timeout(time.Second). 1621 Body(reqBodyExpected). 1622 Do().WasCreated(&wasCreated).Get() 1623 if err != nil { 1624 t.Errorf("Unexpected error: %v %#v", err, err) 1625 return 1626 } 1627 if obj == nil { 1628 t.Error("nil obj") 1629 } else if !apiequality.Semantic.DeepDerivative(expectedObj, obj) { 1630 t.Errorf("Expected: %#v, got %#v", expectedObj, obj) 1631 } 1632 if !wasCreated { 1633 t.Errorf("Expected object was created") 1634 } 1635 1636 tmpStr := string(reqBodyExpected) 1637 requestURL := defaultResourcePathWithPrefix("foo/bar/baz", "", "", "") 1638 requestURL += "?timeout=1s" 1639 fakeHandler.ValidateRequest(t, requestURL, "PUT", &tmpStr) 1640} 1641 1642func TestVerbs(t *testing.T) { 1643 c := testRESTClient(t, nil) 1644 if r := c.Post(); r.verb != "POST" { 1645 t.Errorf("Post verb is wrong") 1646 } 1647 if r := c.Put(); r.verb != "PUT" { 1648 t.Errorf("Put verb is wrong") 1649 } 1650 if r := c.Get(); r.verb != "GET" { 1651 t.Errorf("Get verb is wrong") 1652 } 1653 if r := c.Delete(); r.verb != "DELETE" { 1654 t.Errorf("Delete verb is wrong") 1655 } 1656} 1657 1658func TestAbsPath(t *testing.T) { 1659 for i, tc := range []struct { 1660 configPrefix string 1661 resourcePrefix string 1662 absPath string 1663 wantsAbsPath string 1664 }{ 1665 {"/", "", "", "/"}, 1666 {"", "", "/", "/"}, 1667 {"", "", "/api", "/api"}, 1668 {"", "", "/api/", "/api/"}, 1669 {"", "", "/apis", "/apis"}, 1670 {"", "/foo", "/bar/foo", "/bar/foo"}, 1671 {"", "/api/foo/123", "/bar/foo", "/bar/foo"}, 1672 {"/p1", "", "", "/p1"}, 1673 {"/p1", "", "/", "/p1/"}, 1674 {"/p1", "", "/api", "/p1/api"}, 1675 {"/p1", "", "/apis", "/p1/apis"}, 1676 {"/p1", "/r1", "/apis", "/p1/apis"}, 1677 {"/p1", "/api/r1", "/apis", "/p1/apis"}, 1678 {"/p1/api/p2", "", "", "/p1/api/p2"}, 1679 {"/p1/api/p2", "", "/", "/p1/api/p2/"}, 1680 {"/p1/api/p2", "", "/api", "/p1/api/p2/api"}, 1681 {"/p1/api/p2", "", "/api/", "/p1/api/p2/api/"}, 1682 {"/p1/api/p2", "/r1", "/api/", "/p1/api/p2/api/"}, 1683 {"/p1/api/p2", "/api/r1", "/api/", "/p1/api/p2/api/"}, 1684 } { 1685 u, _ := url.Parse("http://localhost:123" + tc.configPrefix) 1686 r := NewRequest(nil, "POST", u, "", ContentConfig{GroupVersion: &schema.GroupVersion{Group: "test"}}, Serializers{}, nil, nil, 0).Prefix(tc.resourcePrefix).AbsPath(tc.absPath) 1687 if r.pathPrefix != tc.wantsAbsPath { 1688 t.Errorf("test case %d failed, unexpected path: %q, expected %q", i, r.pathPrefix, tc.wantsAbsPath) 1689 } 1690 } 1691} 1692 1693func TestUnacceptableParamNames(t *testing.T) { 1694 table := []struct { 1695 name string 1696 testVal string 1697 expectSuccess bool 1698 }{ 1699 // timeout is no longer "protected" 1700 {"timeout", "42", true}, 1701 } 1702 1703 for _, item := range table { 1704 c := testRESTClient(t, nil) 1705 r := c.Get().setParam(item.name, item.testVal) 1706 if e, a := item.expectSuccess, r.err == nil; e != a { 1707 t.Errorf("expected %v, got %v (%v)", e, a, r.err) 1708 } 1709 } 1710} 1711 1712func TestBody(t *testing.T) { 1713 const data = "test payload" 1714 1715 obj := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} 1716 bodyExpected, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), obj) 1717 1718 f, err := ioutil.TempFile("", "test_body") 1719 if err != nil { 1720 t.Fatalf("TempFile error: %v", err) 1721 } 1722 if _, err := f.WriteString(data); err != nil { 1723 t.Fatalf("TempFile.WriteString error: %v", err) 1724 } 1725 f.Close() 1726 defer os.Remove(f.Name()) 1727 1728 var nilObject *metav1.DeleteOptions 1729 typedObject := interface{}(nilObject) 1730 c := testRESTClient(t, nil) 1731 tests := []struct { 1732 input interface{} 1733 expected string 1734 headers map[string]string 1735 }{ 1736 {[]byte(data), data, nil}, 1737 {f.Name(), data, nil}, 1738 {strings.NewReader(data), data, nil}, 1739 {obj, string(bodyExpected), map[string]string{"Content-Type": "application/json"}}, 1740 {typedObject, "", nil}, 1741 } 1742 for i, tt := range tests { 1743 r := c.Post().Body(tt.input) 1744 if r.err != nil { 1745 t.Errorf("%d: r.Body(%#v) error: %v", i, tt, r.err) 1746 continue 1747 } 1748 if tt.headers != nil { 1749 for k, v := range tt.headers { 1750 if r.headers.Get(k) != v { 1751 t.Errorf("%d: r.headers[%q] = %q; want %q", i, k, v, v) 1752 } 1753 } 1754 } 1755 1756 if r.body == nil { 1757 if len(tt.expected) != 0 { 1758 t.Errorf("%d: r.body = %q; want %q", i, r.body, tt.expected) 1759 } 1760 continue 1761 } 1762 buf := make([]byte, len(tt.expected)) 1763 if _, err := r.body.Read(buf); err != nil { 1764 t.Errorf("%d: r.body.Read error: %v", i, err) 1765 continue 1766 } 1767 body := string(buf) 1768 if body != tt.expected { 1769 t.Errorf("%d: r.body = %q; want %q", i, body, tt.expected) 1770 } 1771 } 1772} 1773 1774func TestWatch(t *testing.T) { 1775 var table = []struct { 1776 t watch.EventType 1777 obj runtime.Object 1778 }{ 1779 {watch.Added, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "first"}}}, 1780 {watch.Modified, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "second"}}}, 1781 {watch.Deleted, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "last"}}}, 1782 } 1783 1784 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1785 flusher, ok := w.(http.Flusher) 1786 if !ok { 1787 panic("need flusher!") 1788 } 1789 1790 w.Header().Set("Transfer-Encoding", "chunked") 1791 w.WriteHeader(http.StatusOK) 1792 flusher.Flush() 1793 1794 encoder := restclientwatch.NewEncoder(streaming.NewEncoder(w, scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)), scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)) 1795 for _, item := range table { 1796 if err := encoder.Encode(&watch.Event{Type: item.t, Object: item.obj}); err != nil { 1797 panic(err) 1798 } 1799 flusher.Flush() 1800 } 1801 })) 1802 defer testServer.Close() 1803 1804 s := testRESTClient(t, testServer) 1805 watching, err := s.Get().Prefix("path/to/watch/thing").Watch() 1806 if err != nil { 1807 t.Fatalf("Unexpected error") 1808 } 1809 1810 for _, item := range table { 1811 got, ok := <-watching.ResultChan() 1812 if !ok { 1813 t.Fatalf("Unexpected early close") 1814 } 1815 if e, a := item.t, got.Type; e != a { 1816 t.Errorf("Expected %v, got %v", e, a) 1817 } 1818 if e, a := item.obj, got.Object; !apiequality.Semantic.DeepDerivative(e, a) { 1819 t.Errorf("Expected %v, got %v", e, a) 1820 } 1821 } 1822 1823 _, ok := <-watching.ResultChan() 1824 if ok { 1825 t.Fatal("Unexpected non-close") 1826 } 1827} 1828 1829func TestStream(t *testing.T) { 1830 expectedBody := "expected body" 1831 1832 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1833 flusher, ok := w.(http.Flusher) 1834 if !ok { 1835 panic("need flusher!") 1836 } 1837 w.Header().Set("Transfer-Encoding", "chunked") 1838 w.WriteHeader(http.StatusOK) 1839 w.Write([]byte(expectedBody)) 1840 flusher.Flush() 1841 })) 1842 defer testServer.Close() 1843 1844 s := testRESTClient(t, testServer) 1845 readCloser, err := s.Get().Prefix("path/to/stream/thing").Stream() 1846 if err != nil { 1847 t.Fatalf("unexpected error: %v", err) 1848 } 1849 defer readCloser.Close() 1850 buf := new(bytes.Buffer) 1851 buf.ReadFrom(readCloser) 1852 resultBody := buf.String() 1853 1854 if expectedBody != resultBody { 1855 t.Errorf("Expected %s, got %s", expectedBody, resultBody) 1856 } 1857} 1858 1859func testRESTClient(t testing.TB, srv *httptest.Server) *RESTClient { 1860 baseURL, _ := url.Parse("http://localhost") 1861 if srv != nil { 1862 var err error 1863 baseURL, err = url.Parse(srv.URL) 1864 if err != nil { 1865 t.Fatalf("failed to parse test URL: %v", err) 1866 } 1867 } 1868 versionedAPIPath := defaultResourcePathWithPrefix("", "", "", "") 1869 client, err := NewRESTClient(baseURL, versionedAPIPath, defaultContentConfig(), 0, 0, nil, nil) 1870 if err != nil { 1871 t.Fatalf("failed to create a client: %v", err) 1872 } 1873 return client 1874} 1875 1876func TestDoContext(t *testing.T) { 1877 receivedCh := make(chan struct{}) 1878 block := make(chan struct{}) 1879 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1880 close(receivedCh) 1881 <-block 1882 w.WriteHeader(http.StatusOK) 1883 })) 1884 defer testServer.Close() 1885 defer close(block) 1886 1887 ctx, cancel := context.WithCancel(context.Background()) 1888 defer cancel() 1889 1890 go func() { 1891 <-receivedCh 1892 cancel() 1893 }() 1894 1895 c := testRESTClient(t, testServer) 1896 _, err := c.Verb("GET"). 1897 Context(ctx). 1898 Prefix("foo"). 1899 DoRaw() 1900 if err == nil { 1901 t.Fatal("Expected context cancellation error") 1902 } 1903} 1904 1905func buildString(length int) string { 1906 s := make([]byte, length) 1907 for i := range s { 1908 s[i] = 'a' 1909 } 1910 return string(s) 1911} 1912 1913func init() { 1914 klog.InitFlags(nil) 1915} 1916 1917func TestTruncateBody(t *testing.T) { 1918 tests := []struct { 1919 body string 1920 want string 1921 level string 1922 }{ 1923 // Anything below 8 is completely truncated 1924 { 1925 body: "Completely truncated below 8", 1926 want: " [truncated 28 chars]", 1927 level: "0", 1928 }, 1929 // Small strings are not truncated by high levels 1930 { 1931 body: "Small body never gets truncated", 1932 want: "Small body never gets truncated", 1933 level: "10", 1934 }, 1935 { 1936 body: "Small body never gets truncated", 1937 want: "Small body never gets truncated", 1938 level: "8", 1939 }, 1940 // Strings are truncated to 1024 if level is less than 9. 1941 { 1942 body: buildString(2000), 1943 level: "8", 1944 want: fmt.Sprintf("%s [truncated 976 chars]", buildString(1024)), 1945 }, 1946 // Strings are truncated to 10240 if level is 9. 1947 { 1948 body: buildString(20000), 1949 level: "9", 1950 want: fmt.Sprintf("%s [truncated 9760 chars]", buildString(10240)), 1951 }, 1952 // Strings are not truncated if level is 10 or higher 1953 { 1954 body: buildString(20000), 1955 level: "10", 1956 want: buildString(20000), 1957 }, 1958 // Strings are not truncated if level is 10 or higher 1959 { 1960 body: buildString(20000), 1961 level: "11", 1962 want: buildString(20000), 1963 }, 1964 } 1965 1966 l := flag.Lookup("v").Value.(flag.Getter).Get().(klog.Level) 1967 for _, test := range tests { 1968 flag.Set("v", test.level) 1969 got := truncateBody(test.body) 1970 if got != test.want { 1971 t.Errorf("truncateBody(%v) = %v, want %v", test.body, got, test.want) 1972 } 1973 } 1974 flag.Set("v", l.String()) 1975} 1976 1977func defaultResourcePathWithPrefix(prefix, resource, namespace, name string) string { 1978 var path string 1979 path = "/api/" + v1.SchemeGroupVersion.Version 1980 1981 if prefix != "" { 1982 path = path + "/" + prefix 1983 } 1984 if namespace != "" { 1985 path = path + "/namespaces/" + namespace 1986 } 1987 // Resource names are lower case. 1988 resource = strings.ToLower(resource) 1989 if resource != "" { 1990 path = path + "/" + resource 1991 } 1992 if name != "" { 1993 path = path + "/" + name 1994 } 1995 return path 1996} 1997