| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package rest |
| |
| import ( |
| "net/http" |
| "net/http/httptest" |
| "net/url" |
| "os" |
| "reflect" |
| "testing" |
| "time" |
| |
| "fmt" |
| |
| "k8s.io/api/core/v1" |
| v1beta1 "k8s.io/api/extensions/v1beta1" |
| "k8s.io/apimachinery/pkg/api/errors" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/serializer" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/diff" |
| "k8s.io/client-go/kubernetes/scheme" |
| utiltesting "k8s.io/client-go/util/testing" |
| ) |
| |
| type TestParam struct { |
| actualError error |
| expectingError bool |
| actualCreated bool |
| expCreated bool |
| expStatus *metav1.Status |
| testBody bool |
| testBodyErrorIsNotNil bool |
| } |
| |
| // TestSerializer makes sure that you're always able to decode metav1.Status |
| func TestSerializer(t *testing.T) { |
| gv := v1beta1.SchemeGroupVersion |
| contentConfig := ContentConfig{ |
| ContentType: "application/json", |
| GroupVersion: &gv, |
| NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, |
| } |
| |
| serializer, err := createSerializers(contentConfig) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // bytes based on actual return from API server when encoding an "unversioned" object |
| obj, err := runtime.Decode(serializer.Decoder, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`)) |
| t.Log(obj) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestDoRequestSuccess(t *testing.T) { |
| testServer, fakeHandler, status := testServerEnv(t, 200) |
| defer testServer.Close() |
| |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| body, err := c.Get().Prefix("test").Do().Raw() |
| |
| testParam := TestParam{actualError: err, expectingError: false, expCreated: true, |
| expStatus: status, testBody: true, testBodyErrorIsNotNil: false} |
| validate(testParam, t, body, fakeHandler) |
| } |
| |
| func TestDoRequestFailed(t *testing.T) { |
| status := &metav1.Status{ |
| Code: http.StatusNotFound, |
| Status: metav1.StatusFailure, |
| Reason: metav1.StatusReasonNotFound, |
| Message: " \"\" not found", |
| Details: &metav1.StatusDetails{}, |
| } |
| expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) |
| fakeHandler := utiltesting.FakeHandler{ |
| StatusCode: 404, |
| ResponseBody: string(expectedBody), |
| T: t, |
| } |
| testServer := httptest.NewServer(&fakeHandler) |
| defer testServer.Close() |
| |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| err = c.Get().Do().Error() |
| if err == nil { |
| t.Errorf("unexpected non-error") |
| } |
| ss, ok := err.(errors.APIStatus) |
| if !ok { |
| t.Errorf("unexpected error type %v", err) |
| } |
| actual := ss.Status() |
| if !reflect.DeepEqual(status, &actual) { |
| t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) |
| } |
| } |
| |
| func TestDoRawRequestFailed(t *testing.T) { |
| status := &metav1.Status{ |
| Code: http.StatusNotFound, |
| Status: metav1.StatusFailure, |
| Reason: metav1.StatusReasonNotFound, |
| Message: "the server could not find the requested resource", |
| Details: &metav1.StatusDetails{ |
| Causes: []metav1.StatusCause{ |
| {Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"}, |
| }, |
| }, |
| } |
| expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) |
| fakeHandler := utiltesting.FakeHandler{ |
| StatusCode: 404, |
| ResponseBody: string(expectedBody), |
| T: t, |
| } |
| testServer := httptest.NewServer(&fakeHandler) |
| defer testServer.Close() |
| |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| body, err := c.Get().Do().Raw() |
| |
| if err == nil || body == nil { |
| t.Errorf("unexpected non-error: %#v", body) |
| } |
| ss, ok := err.(errors.APIStatus) |
| if !ok { |
| t.Errorf("unexpected error type %v", err) |
| } |
| actual := ss.Status() |
| if !reflect.DeepEqual(status, &actual) { |
| t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) |
| } |
| } |
| |
| func TestDoRequestCreated(t *testing.T) { |
| testServer, fakeHandler, status := testServerEnv(t, 201) |
| defer testServer.Close() |
| |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| created := false |
| body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |
| |
| testParam := TestParam{actualError: err, expectingError: false, expCreated: true, |
| expStatus: status, testBody: false} |
| validate(testParam, t, body, fakeHandler) |
| } |
| |
| func TestDoRequestNotCreated(t *testing.T) { |
| testServer, fakeHandler, expectedStatus := testServerEnv(t, 202) |
| defer testServer.Close() |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| created := false |
| body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |
| testParam := TestParam{actualError: err, expectingError: false, expCreated: false, |
| expStatus: expectedStatus, testBody: false} |
| validate(testParam, t, body, fakeHandler) |
| } |
| |
| func TestDoRequestAcceptedNoContentReturned(t *testing.T) { |
| testServer, fakeHandler, _ := testServerEnv(t, 204) |
| defer testServer.Close() |
| |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| created := false |
| body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |
| testParam := TestParam{actualError: err, expectingError: false, expCreated: false, |
| testBody: false} |
| validate(testParam, t, body, fakeHandler) |
| } |
| |
| func TestBadRequest(t *testing.T) { |
| testServer, fakeHandler, _ := testServerEnv(t, 400) |
| defer testServer.Close() |
| c, err := restClient(testServer) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| created := false |
| body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |
| testParam := TestParam{actualError: err, expectingError: true, expCreated: false, |
| testBody: true} |
| validate(testParam, t, body, fakeHandler) |
| } |
| |
| func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utiltesting.FakeHandler) { |
| switch { |
| case testParam.expectingError && testParam.actualError == nil: |
| t.Errorf("Expected error") |
| case !testParam.expectingError && testParam.actualError != nil: |
| t.Error(testParam.actualError) |
| } |
| if !testParam.expCreated { |
| if testParam.actualCreated { |
| t.Errorf("Expected object not to be created") |
| } |
| } |
| statusOut, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), body) |
| if testParam.testBody { |
| if testParam.testBodyErrorIsNotNil && err == nil { |
| t.Errorf("Expected Error") |
| } |
| if !testParam.testBodyErrorIsNotNil && err != nil { |
| t.Errorf("Unexpected Error: %v", err) |
| } |
| } |
| |
| if testParam.expStatus != nil { |
| if !reflect.DeepEqual(testParam.expStatus, statusOut) { |
| t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", testParam.expStatus, statusOut) |
| } |
| } |
| fakeHandler.ValidateRequest(t, "/"+v1.SchemeGroupVersion.String()+"/test", "GET", nil) |
| |
| } |
| |
| func TestHttpMethods(t *testing.T) { |
| testServer, _, _ := testServerEnv(t, 200) |
| defer testServer.Close() |
| c, _ := restClient(testServer) |
| |
| request := c.Post() |
| if request == nil { |
| t.Errorf("Post : Object returned should not be nil") |
| } |
| |
| request = c.Get() |
| if request == nil { |
| t.Errorf("Get: Object returned should not be nil") |
| } |
| |
| request = c.Put() |
| if request == nil { |
| t.Errorf("Put : Object returned should not be nil") |
| } |
| |
| request = c.Delete() |
| if request == nil { |
| t.Errorf("Delete : Object returned should not be nil") |
| } |
| |
| request = c.Patch(types.JSONPatchType) |
| if request == nil { |
| t.Errorf("Patch : Object returned should not be nil") |
| } |
| } |
| |
| func TestCreateBackoffManager(t *testing.T) { |
| |
| theUrl, _ := url.Parse("http://localhost") |
| |
| // 1 second base backoff + duration of 2 seconds -> exponential backoff for requests. |
| os.Setenv(envBackoffBase, "1") |
| os.Setenv(envBackoffDuration, "2") |
| backoff := readExpBackoffConfig() |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| if backoff.CalculateBackoff(theUrl)/time.Second != 2 { |
| t.Errorf("Backoff env not working.") |
| } |
| |
| // 0 duration -> no backoff. |
| os.Setenv(envBackoffBase, "1") |
| os.Setenv(envBackoffDuration, "0") |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| backoff = readExpBackoffConfig() |
| if backoff.CalculateBackoff(theUrl)/time.Second != 0 { |
| t.Errorf("Zero backoff duration, but backoff still occurring.") |
| } |
| |
| // No env -> No backoff. |
| os.Setenv(envBackoffBase, "") |
| os.Setenv(envBackoffDuration, "") |
| backoff = readExpBackoffConfig() |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| backoff.UpdateBackoff(theUrl, nil, 500) |
| if backoff.CalculateBackoff(theUrl)/time.Second != 0 { |
| t.Errorf("Backoff should have been 0.") |
| } |
| |
| } |
| |
| func testServerEnv(t *testing.T, statusCode int) (*httptest.Server, *utiltesting.FakeHandler, *metav1.Status) { |
| status := &metav1.Status{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Status"}, Status: fmt.Sprintf("%s", metav1.StatusSuccess)} |
| expectedBody, _ := runtime.Encode(scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), status) |
| fakeHandler := utiltesting.FakeHandler{ |
| StatusCode: statusCode, |
| ResponseBody: string(expectedBody), |
| T: t, |
| } |
| testServer := httptest.NewServer(&fakeHandler) |
| return testServer, &fakeHandler, status |
| } |
| |
| func restClient(testServer *httptest.Server) (*RESTClient, error) { |
| c, err := RESTClientFor(&Config{ |
| Host: testServer.URL, |
| ContentConfig: ContentConfig{ |
| GroupVersion: &v1.SchemeGroupVersion, |
| NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, |
| }, |
| Username: "user", |
| Password: "pass", |
| }) |
| return c, err |
| } |