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