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