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	"net/http"
21	"net/http/httptest"
22	"net/url"
23	"os"
24	"reflect"
25	"testing"
26	"time"
27
28	"fmt"
29
30	"k8s.io/api/core/v1"
31	v1beta1 "k8s.io/api/extensions/v1beta1"
32	"k8s.io/apimachinery/pkg/api/errors"
33	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34	"k8s.io/apimachinery/pkg/runtime"
35	"k8s.io/apimachinery/pkg/types"
36	"k8s.io/apimachinery/pkg/util/diff"
37	"k8s.io/client-go/kubernetes/scheme"
38	utiltesting "k8s.io/client-go/util/testing"
39)
40
41type TestParam struct {
42	actualError           error
43	expectingError        bool
44	actualCreated         bool
45	expCreated            bool
46	expStatus             *metav1.Status
47	testBody              bool
48	testBodyErrorIsNotNil bool
49}
50
51// TestSerializer makes sure that you're always able to decode metav1.Status
52func TestSerializer(t *testing.T) {
53	gv := v1beta1.SchemeGroupVersion
54	contentConfig := ContentConfig{
55		ContentType:          "application/json",
56		GroupVersion:         &gv,
57		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
58	}
59
60	serializer, err := createSerializers(contentConfig)
61	if err != nil {
62		t.Fatal(err)
63	}
64	// bytes based on actual return from API server when encoding an "unversioned" object
65	obj, err := runtime.Decode(serializer.Decoder, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`))
66	t.Log(obj)
67	if err != nil {
68		t.Fatal(err)
69	}
70}
71
72func TestDoRequestSuccess(t *testing.T) {
73	testServer, fakeHandler, status := testServerEnv(t, 200)
74	defer testServer.Close()
75
76	c, err := restClient(testServer)
77	if err != nil {
78		t.Fatalf("unexpected error: %v", err)
79	}
80	body, err := c.Get().Prefix("test").Do().Raw()
81
82	testParam := TestParam{actualError: err, expectingError: false, expCreated: true,
83		expStatus: status, testBody: true, testBodyErrorIsNotNil: false}
84	validate(testParam, t, body, fakeHandler)
85}
86
87func TestDoRequestFailed(t *testing.T) {
88	status := &metav1.Status{
89		Code:    http.StatusNotFound,
90		Status:  metav1.StatusFailure,
91		Reason:  metav1.StatusReasonNotFound,
92		Message: " \"\" not found",
93		Details: &metav1.StatusDetails{},
94	}
95	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
96	fakeHandler := utiltesting.FakeHandler{
97		StatusCode:   404,
98		ResponseBody: string(expectedBody),
99		T:            t,
100	}
101	testServer := httptest.NewServer(&fakeHandler)
102	defer testServer.Close()
103
104	c, err := restClient(testServer)
105	if err != nil {
106		t.Fatalf("unexpected error: %v", err)
107	}
108	err = c.Get().Do().Error()
109	if err == nil {
110		t.Errorf("unexpected non-error")
111	}
112	ss, ok := err.(errors.APIStatus)
113	if !ok {
114		t.Errorf("unexpected error type %v", err)
115	}
116	actual := ss.Status()
117	if !reflect.DeepEqual(status, &actual) {
118		t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual))
119	}
120}
121
122func TestDoRawRequestFailed(t *testing.T) {
123	status := &metav1.Status{
124		Code:    http.StatusNotFound,
125		Status:  metav1.StatusFailure,
126		Reason:  metav1.StatusReasonNotFound,
127		Message: "the server could not find the requested resource",
128		Details: &metav1.StatusDetails{
129			Causes: []metav1.StatusCause{
130				{Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"},
131			},
132		},
133	}
134	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
135	fakeHandler := utiltesting.FakeHandler{
136		StatusCode:   404,
137		ResponseBody: string(expectedBody),
138		T:            t,
139	}
140	testServer := httptest.NewServer(&fakeHandler)
141	defer testServer.Close()
142
143	c, err := restClient(testServer)
144	if err != nil {
145		t.Fatalf("unexpected error: %v", err)
146	}
147	body, err := c.Get().Do().Raw()
148
149	if err == nil || body == nil {
150		t.Errorf("unexpected non-error: %#v", body)
151	}
152	ss, ok := err.(errors.APIStatus)
153	if !ok {
154		t.Errorf("unexpected error type %v", err)
155	}
156	actual := ss.Status()
157	if !reflect.DeepEqual(status, &actual) {
158		t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual))
159	}
160}
161
162func TestDoRequestCreated(t *testing.T) {
163	testServer, fakeHandler, status := testServerEnv(t, 201)
164	defer testServer.Close()
165
166	c, err := restClient(testServer)
167	if err != nil {
168		t.Fatalf("unexpected error: %v", err)
169	}
170	created := false
171	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw()
172
173	testParam := TestParam{actualError: err, expectingError: false, expCreated: true,
174		expStatus: status, testBody: false}
175	validate(testParam, t, body, fakeHandler)
176}
177
178func TestDoRequestNotCreated(t *testing.T) {
179	testServer, fakeHandler, expectedStatus := testServerEnv(t, 202)
180	defer testServer.Close()
181	c, err := restClient(testServer)
182	if err != nil {
183		t.Fatalf("unexpected error: %v", err)
184	}
185	created := false
186	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw()
187	testParam := TestParam{actualError: err, expectingError: false, expCreated: false,
188		expStatus: expectedStatus, testBody: false}
189	validate(testParam, t, body, fakeHandler)
190}
191
192func TestDoRequestAcceptedNoContentReturned(t *testing.T) {
193	testServer, fakeHandler, _ := testServerEnv(t, 204)
194	defer testServer.Close()
195
196	c, err := restClient(testServer)
197	if err != nil {
198		t.Fatalf("unexpected error: %v", err)
199	}
200	created := false
201	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw()
202	testParam := TestParam{actualError: err, expectingError: false, expCreated: false,
203		testBody: false}
204	validate(testParam, t, body, fakeHandler)
205}
206
207func TestBadRequest(t *testing.T) {
208	testServer, fakeHandler, _ := testServerEnv(t, 400)
209	defer testServer.Close()
210	c, err := restClient(testServer)
211	if err != nil {
212		t.Fatalf("unexpected error: %v", err)
213	}
214	created := false
215	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw()
216	testParam := TestParam{actualError: err, expectingError: true, expCreated: false,
217		testBody: true}
218	validate(testParam, t, body, fakeHandler)
219}
220
221func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utiltesting.FakeHandler) {
222	switch {
223	case testParam.expectingError && testParam.actualError == nil:
224		t.Errorf("Expected error")
225	case !testParam.expectingError && testParam.actualError != nil:
226		t.Error(testParam.actualError)
227	}
228	if !testParam.expCreated {
229		if testParam.actualCreated {
230			t.Errorf("Expected object not to be created")
231		}
232	}
233	statusOut, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), body)
234	if testParam.testBody {
235		if testParam.testBodyErrorIsNotNil && err == nil {
236			t.Errorf("Expected Error")
237		}
238		if !testParam.testBodyErrorIsNotNil && err != nil {
239			t.Errorf("Unexpected Error: %v", err)
240		}
241	}
242
243	if testParam.expStatus != nil {
244		if !reflect.DeepEqual(testParam.expStatus, statusOut) {
245			t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", testParam.expStatus, statusOut)
246		}
247	}
248	fakeHandler.ValidateRequest(t, "/"+v1.SchemeGroupVersion.String()+"/test", "GET", nil)
249
250}
251
252func TestHttpMethods(t *testing.T) {
253	testServer, _, _ := testServerEnv(t, 200)
254	defer testServer.Close()
255	c, _ := restClient(testServer)
256
257	request := c.Post()
258	if request == nil {
259		t.Errorf("Post : Object returned should not be nil")
260	}
261
262	request = c.Get()
263	if request == nil {
264		t.Errorf("Get: Object returned should not be nil")
265	}
266
267	request = c.Put()
268	if request == nil {
269		t.Errorf("Put : Object returned should not be nil")
270	}
271
272	request = c.Delete()
273	if request == nil {
274		t.Errorf("Delete : Object returned should not be nil")
275	}
276
277	request = c.Patch(types.JSONPatchType)
278	if request == nil {
279		t.Errorf("Patch : Object returned should not be nil")
280	}
281}
282
283func TestCreateBackoffManager(t *testing.T) {
284
285	theUrl, _ := url.Parse("http://localhost")
286
287	// 1 second base backoff + duration of 2 seconds -> exponential backoff for requests.
288	os.Setenv(envBackoffBase, "1")
289	os.Setenv(envBackoffDuration, "2")
290	backoff := readExpBackoffConfig()
291	backoff.UpdateBackoff(theUrl, nil, 500)
292	backoff.UpdateBackoff(theUrl, nil, 500)
293	if backoff.CalculateBackoff(theUrl)/time.Second != 2 {
294		t.Errorf("Backoff env not working.")
295	}
296
297	// 0 duration -> no backoff.
298	os.Setenv(envBackoffBase, "1")
299	os.Setenv(envBackoffDuration, "0")
300	backoff.UpdateBackoff(theUrl, nil, 500)
301	backoff.UpdateBackoff(theUrl, nil, 500)
302	backoff = readExpBackoffConfig()
303	if backoff.CalculateBackoff(theUrl)/time.Second != 0 {
304		t.Errorf("Zero backoff duration, but backoff still occurring.")
305	}
306
307	// No env -> No backoff.
308	os.Setenv(envBackoffBase, "")
309	os.Setenv(envBackoffDuration, "")
310	backoff = readExpBackoffConfig()
311	backoff.UpdateBackoff(theUrl, nil, 500)
312	backoff.UpdateBackoff(theUrl, nil, 500)
313	if backoff.CalculateBackoff(theUrl)/time.Second != 0 {
314		t.Errorf("Backoff should have been 0.")
315	}
316
317}
318
319func testServerEnv(t *testing.T, statusCode int) (*httptest.Server, *utiltesting.FakeHandler, *metav1.Status) {
320	status := &metav1.Status{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Status"}, Status: fmt.Sprintf("%s", metav1.StatusSuccess)}
321	expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status)
322	fakeHandler := utiltesting.FakeHandler{
323		StatusCode:   statusCode,
324		ResponseBody: string(expectedBody),
325		T:            t,
326	}
327	testServer := httptest.NewServer(&fakeHandler)
328	return testServer, &fakeHandler, status
329}
330
331func restClient(testServer *httptest.Server) (*RESTClient, error) {
332	c, err := RESTClientFor(&Config{
333		Host: testServer.URL,
334		ContentConfig: ContentConfig{
335			GroupVersion:         &v1.SchemeGroupVersion,
336			NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
337		},
338		Username: "user",
339		Password: "pass",
340	})
341	return c, err
342}
343