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