k8s.io/client-go@v0.22.2/rest/client_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rest 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "net/http/httptest" 24 "net/http/httputil" 25 "net/url" 26 "os" 27 "reflect" 28 "testing" 29 "time" 30 31 v1 "k8s.io/api/core/v1" 32 v1beta1 "k8s.io/api/extensions/v1beta1" 33 "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/util/diff" 38 "k8s.io/client-go/kubernetes/scheme" 39 utiltesting "k8s.io/client-go/util/testing" 40 41 "github.com/google/go-cmp/cmp" 42 ) 43 44 type TestParam struct { 45 actualError error 46 expectingError bool 47 actualCreated bool 48 expCreated bool 49 expStatus *metav1.Status 50 testBody bool 51 testBodyErrorIsNotNil bool 52 } 53 54 // TestSerializer makes sure that you're always able to decode metav1.Status 55 func TestSerializer(t *testing.T) { 56 gv := v1beta1.SchemeGroupVersion 57 contentConfig := ContentConfig{ 58 ContentType: "application/json", 59 GroupVersion: &gv, 60 NegotiatedSerializer: scheme.Codecs.WithoutConversion(), 61 } 62 63 n := runtime.NewClientNegotiator(contentConfig.NegotiatedSerializer, gv) 64 d, err := n.Decoder("application/json", nil) 65 if err != nil { 66 t.Fatal(err) 67 } 68 69 // bytes based on actual return from API server when encoding an "unversioned" object 70 obj, err := runtime.Decode(d, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`)) 71 t.Log(obj) 72 if err != nil { 73 t.Fatal(err) 74 } 75 } 76 77 func TestDoRequestSuccess(t *testing.T) { 78 testServer, fakeHandler, status := testServerEnv(t, 200) 79 defer testServer.Close() 80 81 c, err := restClient(testServer) 82 if err != nil { 83 t.Fatalf("unexpected error: %v", err) 84 } 85 body, err := c.Get().Prefix("test").Do(context.Background()).Raw() 86 87 testParam := TestParam{actualError: err, expectingError: false, expCreated: true, 88 expStatus: status, testBody: true, testBodyErrorIsNotNil: false} 89 validate(testParam, t, body, fakeHandler) 90 } 91 92 func TestDoRequestFailed(t *testing.T) { 93 status := &metav1.Status{ 94 Code: http.StatusNotFound, 95 Status: metav1.StatusFailure, 96 Reason: metav1.StatusReasonNotFound, 97 Message: " \"\" not found", 98 Details: &metav1.StatusDetails{}, 99 } 100 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) 101 fakeHandler := utiltesting.FakeHandler{ 102 StatusCode: 404, 103 ResponseBody: string(expectedBody), 104 T: t, 105 } 106 testServer := httptest.NewServer(&fakeHandler) 107 defer testServer.Close() 108 109 c, err := restClient(testServer) 110 if err != nil { 111 t.Fatalf("unexpected error: %v", err) 112 } 113 err = c.Get().Do(context.Background()).Error() 114 if err == nil { 115 t.Errorf("unexpected non-error") 116 } 117 ss, ok := err.(errors.APIStatus) 118 if !ok { 119 t.Errorf("unexpected error type %v", err) 120 } 121 actual := ss.Status() 122 if !reflect.DeepEqual(status, &actual) { 123 t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) 124 } 125 } 126 127 func TestDoRawRequestFailed(t *testing.T) { 128 status := &metav1.Status{ 129 Code: http.StatusNotFound, 130 Status: metav1.StatusFailure, 131 Reason: metav1.StatusReasonNotFound, 132 Message: "the server could not find the requested resource", 133 Details: &metav1.StatusDetails{ 134 Causes: []metav1.StatusCause{ 135 {Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"}, 136 }, 137 }, 138 } 139 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) 140 fakeHandler := utiltesting.FakeHandler{ 141 StatusCode: 404, 142 ResponseBody: string(expectedBody), 143 T: t, 144 } 145 testServer := httptest.NewServer(&fakeHandler) 146 defer testServer.Close() 147 148 c, err := restClient(testServer) 149 if err != nil { 150 t.Fatalf("unexpected error: %v", err) 151 } 152 body, err := c.Get().Do(context.Background()).Raw() 153 154 if err == nil || body == nil { 155 t.Errorf("unexpected non-error: %#v", body) 156 } 157 ss, ok := err.(errors.APIStatus) 158 if !ok { 159 t.Errorf("unexpected error type %v", err) 160 } 161 actual := ss.Status() 162 if !reflect.DeepEqual(status, &actual) { 163 t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) 164 } 165 } 166 167 func TestDoRequestCreated(t *testing.T) { 168 testServer, fakeHandler, status := testServerEnv(t, 201) 169 defer testServer.Close() 170 171 c, err := restClient(testServer) 172 if err != nil { 173 t.Fatalf("unexpected error: %v", err) 174 } 175 created := false 176 body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw() 177 178 testParam := TestParam{actualError: err, expectingError: false, expCreated: true, 179 expStatus: status, testBody: false} 180 validate(testParam, t, body, fakeHandler) 181 } 182 183 func TestDoRequestNotCreated(t *testing.T) { 184 testServer, fakeHandler, expectedStatus := testServerEnv(t, 202) 185 defer testServer.Close() 186 c, err := restClient(testServer) 187 if err != nil { 188 t.Fatalf("unexpected error: %v", err) 189 } 190 created := false 191 body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw() 192 testParam := TestParam{actualError: err, expectingError: false, expCreated: false, 193 expStatus: expectedStatus, testBody: false} 194 validate(testParam, t, body, fakeHandler) 195 } 196 197 func TestDoRequestAcceptedNoContentReturned(t *testing.T) { 198 testServer, fakeHandler, _ := testServerEnv(t, 204) 199 defer testServer.Close() 200 201 c, err := restClient(testServer) 202 if err != nil { 203 t.Fatalf("unexpected error: %v", err) 204 } 205 created := false 206 body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw() 207 testParam := TestParam{actualError: err, expectingError: false, expCreated: false, 208 testBody: false} 209 validate(testParam, t, body, fakeHandler) 210 } 211 212 func TestBadRequest(t *testing.T) { 213 testServer, fakeHandler, _ := testServerEnv(t, 400) 214 defer testServer.Close() 215 c, err := restClient(testServer) 216 if err != nil { 217 t.Fatalf("unexpected error: %v", err) 218 } 219 created := false 220 body, err := c.Get().Prefix("test").Do(context.Background()).WasCreated(&created).Raw() 221 testParam := TestParam{actualError: err, expectingError: true, expCreated: false, 222 testBody: true} 223 validate(testParam, t, body, fakeHandler) 224 } 225 226 func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utiltesting.FakeHandler) { 227 switch { 228 case testParam.expectingError && testParam.actualError == nil: 229 t.Errorf("Expected error") 230 case !testParam.expectingError && testParam.actualError != nil: 231 t.Error(testParam.actualError) 232 } 233 if !testParam.expCreated { 234 if testParam.actualCreated { 235 t.Errorf("Expected object not to be created") 236 } 237 } 238 statusOut, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), body) 239 if testParam.testBody { 240 if testParam.testBodyErrorIsNotNil && err == nil { 241 t.Errorf("Expected Error") 242 } 243 if !testParam.testBodyErrorIsNotNil && err != nil { 244 t.Errorf("Unexpected Error: %v", err) 245 } 246 } 247 248 if testParam.expStatus != nil { 249 if !reflect.DeepEqual(testParam.expStatus, statusOut) { 250 t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", testParam.expStatus, statusOut) 251 } 252 } 253 fakeHandler.ValidateRequest(t, "/"+v1.SchemeGroupVersion.String()+"/test", "GET", nil) 254 255 } 256 257 func TestHTTPMethods(t *testing.T) { 258 testServer, _, _ := testServerEnv(t, 200) 259 defer testServer.Close() 260 c, _ := restClient(testServer) 261 262 request := c.Post() 263 if request == nil { 264 t.Errorf("Post : Object returned should not be nil") 265 } 266 267 request = c.Get() 268 if request == nil { 269 t.Errorf("Get: Object returned should not be nil") 270 } 271 272 request = c.Put() 273 if request == nil { 274 t.Errorf("Put : Object returned should not be nil") 275 } 276 277 request = c.Delete() 278 if request == nil { 279 t.Errorf("Delete : Object returned should not be nil") 280 } 281 282 request = c.Patch(types.JSONPatchType) 283 if request == nil { 284 t.Errorf("Patch : Object returned should not be nil") 285 } 286 } 287 288 func TestHTTPProxy(t *testing.T) { 289 ctx := context.Background() 290 testServer, fh, _ := testServerEnv(t, 200) 291 fh.ResponseBody = "backend data" 292 defer testServer.Close() 293 294 testProxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 295 to, err := url.Parse(req.RequestURI) 296 if err != nil { 297 t.Fatalf("err: %v", err) 298 } 299 w.Write([]byte("proxied: ")) 300 httputil.NewSingleHostReverseProxy(to).ServeHTTP(w, req) 301 })) 302 defer testProxyServer.Close() 303 304 t.Logf(testProxyServer.URL) 305 306 u, err := url.Parse(testProxyServer.URL) 307 if err != nil { 308 t.Fatalf("Failed to parse test proxy server url: %v", err) 309 } 310 311 c, err := RESTClientFor(&Config{ 312 Host: testServer.URL, 313 ContentConfig: ContentConfig{ 314 GroupVersion: &v1.SchemeGroupVersion, 315 NegotiatedSerializer: scheme.Codecs.WithoutConversion(), 316 }, 317 Proxy: http.ProxyURL(u), 318 Username: "user", 319 Password: "pass", 320 }) 321 if err != nil { 322 t.Fatalf("Failed to create client: %v", err) 323 } 324 325 request := c.Get() 326 if request == nil { 327 t.Fatalf("Get: Object returned should not be nil") 328 } 329 330 b, err := request.DoRaw(ctx) 331 if err != nil { 332 t.Fatalf("unexpected err: %v", err) 333 } 334 if got, want := string(b), "proxied: backend data"; !cmp.Equal(got, want) { 335 t.Errorf("unexpected body: %v", cmp.Diff(want, got)) 336 } 337 } 338 339 func TestCreateBackoffManager(t *testing.T) { 340 341 theUrl, _ := url.Parse("http://localhost") 342 343 // 1 second base backoff + duration of 2 seconds -> exponential backoff for requests. 344 os.Setenv(envBackoffBase, "1") 345 os.Setenv(envBackoffDuration, "2") 346 backoff := readExpBackoffConfig() 347 backoff.UpdateBackoff(theUrl, nil, 500) 348 backoff.UpdateBackoff(theUrl, nil, 500) 349 if backoff.CalculateBackoff(theUrl)/time.Second != 2 { 350 t.Errorf("Backoff env not working.") 351 } 352 353 // 0 duration -> no backoff. 354 os.Setenv(envBackoffBase, "1") 355 os.Setenv(envBackoffDuration, "0") 356 backoff.UpdateBackoff(theUrl, nil, 500) 357 backoff.UpdateBackoff(theUrl, nil, 500) 358 backoff = readExpBackoffConfig() 359 if backoff.CalculateBackoff(theUrl)/time.Second != 0 { 360 t.Errorf("Zero backoff duration, but backoff still occurring.") 361 } 362 363 // No env -> No backoff. 364 os.Setenv(envBackoffBase, "") 365 os.Setenv(envBackoffDuration, "") 366 backoff = readExpBackoffConfig() 367 backoff.UpdateBackoff(theUrl, nil, 500) 368 backoff.UpdateBackoff(theUrl, nil, 500) 369 if backoff.CalculateBackoff(theUrl)/time.Second != 0 { 370 t.Errorf("Backoff should have been 0.") 371 } 372 373 } 374 375 func testServerEnv(t *testing.T, statusCode int) (*httptest.Server, *utiltesting.FakeHandler, *metav1.Status) { 376 status := &metav1.Status{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Status"}, Status: fmt.Sprintf("%s", metav1.StatusSuccess)} 377 expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) 378 fakeHandler := utiltesting.FakeHandler{ 379 StatusCode: statusCode, 380 ResponseBody: string(expectedBody), 381 T: t, 382 } 383 testServer := httptest.NewServer(&fakeHandler) 384 return testServer, &fakeHandler, status 385 } 386 387 func restClient(testServer *httptest.Server) (*RESTClient, error) { 388 c, err := RESTClientFor(&Config{ 389 Host: testServer.URL, 390 ContentConfig: ContentConfig{ 391 GroupVersion: &v1.SchemeGroupVersion, 392 NegotiatedSerializer: scheme.Codecs.WithoutConversion(), 393 }, 394 Username: "user", 395 Password: "pass", 396 }) 397 return c, err 398 }