| /* |
| 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 endpoints |
| |
| import ( |
| "bytes" |
| "compress/gzip" |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "math/rand" |
| "net/http" |
| "net/http/httptest" |
| "net/http/httputil" |
| "net/url" |
| "reflect" |
| "strconv" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/emicklei/go-restful" |
| fuzzer "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" |
| apiequality "k8s.io/apimachinery/pkg/api/equality" |
| apierrs "k8s.io/apimachinery/pkg/api/errors" |
| "k8s.io/apimachinery/pkg/api/meta" |
| metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
| metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" |
| "k8s.io/apimachinery/pkg/fields" |
| "k8s.io/apimachinery/pkg/labels" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| "k8s.io/apimachinery/pkg/runtime/serializer" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/diff" |
| utilruntime "k8s.io/apimachinery/pkg/util/runtime" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/watch" |
| "k8s.io/apiserver/pkg/admission" |
| auditinternal "k8s.io/apiserver/pkg/apis/audit" |
| "k8s.io/apiserver/pkg/apis/example" |
| examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer" |
| examplev1 "k8s.io/apiserver/pkg/apis/example/v1" |
| "k8s.io/apiserver/pkg/audit" |
| auditpolicy "k8s.io/apiserver/pkg/audit/policy" |
| genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" |
| "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" |
| "k8s.io/apiserver/pkg/endpoints/request" |
| genericapitesting "k8s.io/apiserver/pkg/endpoints/testing" |
| "k8s.io/apiserver/pkg/features" |
| "k8s.io/apiserver/pkg/registry/rest" |
| "k8s.io/apiserver/pkg/server/filters" |
| utilfeature "k8s.io/apiserver/pkg/util/feature" |
| utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" |
| ) |
| |
| type alwaysMutatingDeny struct{} |
| |
| func (alwaysMutatingDeny) Admit(a admission.Attributes) (err error) { |
| return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications")) |
| } |
| |
| func (alwaysMutatingDeny) Handles(operation admission.Operation) bool { |
| return true |
| } |
| |
| type alwaysValidatingDeny struct{} |
| |
| func (alwaysValidatingDeny) Validate(a admission.Attributes) (err error) { |
| return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications")) |
| } |
| |
| func (alwaysValidatingDeny) Handles(operation admission.Operation) bool { |
| return true |
| } |
| |
| // This creates fake API versions, similar to api/latest.go. |
| var testAPIGroup = "test.group" |
| var testAPIGroup2 = "test.group2" |
| var testInternalGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: runtime.APIVersionInternal} |
| var testGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version"} |
| var newGroupVersion = schema.GroupVersion{Group: testAPIGroup, Version: "version2"} |
| var testGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: "version"} |
| var testInternalGroup2Version = schema.GroupVersion{Group: testAPIGroup2, Version: runtime.APIVersionInternal} |
| var prefix = "apis" |
| |
| var grouplessGroupVersion = schema.GroupVersion{Group: "", Version: "v1"} |
| var grouplessInternalGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal} |
| var grouplessPrefix = "api" |
| |
| var groupVersions = []schema.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion} |
| |
| var scheme = runtime.NewScheme() |
| var codecs = serializer.NewCodecFactory(scheme) |
| |
| var codec = codecs.LegacyCodec(groupVersions...) |
| var testCodec = codecs.LegacyCodec(testGroupVersion) |
| var newCodec = codecs.LegacyCodec(newGroupVersion) |
| var parameterCodec = runtime.NewParameterCodec(scheme) |
| |
| var accessor = meta.NewAccessor() |
| var selfLinker runtime.SelfLinker = accessor |
| var admissionControl admission.Interface |
| |
| func init() { |
| metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) |
| |
| // unnamed core group |
| scheme.AddUnversionedTypes(grouplessGroupVersion, &metav1.Status{}) |
| metav1.AddToGroupVersion(scheme, grouplessGroupVersion) |
| |
| utilruntime.Must(example.AddToScheme(scheme)) |
| utilruntime.Must(examplev1.AddToScheme(scheme)) |
| } |
| |
| func addGrouplessTypes() { |
| scheme.AddKnownTypes(grouplessGroupVersion, |
| &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ListOptions{}, &metav1.ExportOptions{}, |
| &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) |
| scheme.AddKnownTypes(grouplessInternalGroupVersion, |
| &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, |
| &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}) |
| } |
| |
| func addTestTypes() { |
| scheme.AddKnownTypes(testGroupVersion, |
| &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, |
| &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, |
| &genericapitesting.SimpleXGSubresource{}) |
| scheme.AddKnownTypes(testGroupVersion, &examplev1.Pod{}) |
| scheme.AddKnownTypes(testInternalGroupVersion, |
| &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, |
| &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, |
| &genericapitesting.SimpleXGSubresource{}) |
| scheme.AddKnownTypes(testInternalGroupVersion, &example.Pod{}) |
| // Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their |
| // their corresponding internal versions, to verify that the desired group version object is |
| // served in the tests. |
| scheme.AddKnownTypes(testGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{}) |
| scheme.AddKnownTypes(testInternalGroup2Version, &genericapitesting.SimpleXGSubresource{}, &metav1.ExportOptions{}) |
| metav1.AddToGroupVersion(scheme, testGroupVersion) |
| } |
| |
| func addNewTestTypes() { |
| scheme.AddKnownTypes(newGroupVersion, |
| &genericapitesting.Simple{}, &genericapitesting.SimpleList{}, &metav1.ExportOptions{}, |
| &metav1.DeleteOptions{}, &genericapitesting.SimpleGetOptions{}, &genericapitesting.SimpleRoot{}, |
| &examplev1.Pod{}, |
| ) |
| metav1.AddToGroupVersion(scheme, newGroupVersion) |
| } |
| |
| func init() { |
| // Certain API objects are returned regardless of the contents of storage: |
| // api.Status is returned in errors |
| |
| addGrouplessTypes() |
| addTestTypes() |
| addNewTestTypes() |
| |
| scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.WithKind("Simple"), |
| func(label, value string) (string, string, error) { |
| return label, value, nil |
| }, |
| ) |
| scheme.AddFieldLabelConversionFunc(testGroupVersion.WithKind("Simple"), |
| func(label, value string) (string, string, error) { |
| return label, value, nil |
| }, |
| ) |
| scheme.AddFieldLabelConversionFunc(newGroupVersion.WithKind("Simple"), |
| func(label, value string) (string, string, error) { |
| return label, value, nil |
| }, |
| ) |
| } |
| |
| // defaultAPIServer exposes nested objects for testability. |
| type defaultAPIServer struct { |
| http.Handler |
| container *restful.Container |
| } |
| |
| // uses the default settings |
| func handle(storage map[string]rest.Storage) http.Handler { |
| return handleInternal(storage, admissionControl, selfLinker, nil) |
| } |
| |
| // tests using a custom self linker |
| func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler { |
| return handleInternal(storage, admissionControl, selfLinker, nil) |
| } |
| |
| func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker, auditSink audit.Sink) http.Handler { |
| container := restful.NewContainer() |
| container.Router(restful.CurlyRouter{}) |
| mux := container.ServeMux |
| |
| template := APIGroupVersion{ |
| Storage: storage, |
| |
| Creater: scheme, |
| Convertor: scheme, |
| UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), |
| Defaulter: scheme, |
| Typer: scheme, |
| Linker: selfLinker, |
| RootScopedKinds: sets.NewString("SimpleRoot"), |
| |
| ParameterCodec: parameterCodec, |
| |
| Admit: admissionControl, |
| } |
| |
| // groupless v1 version |
| { |
| group := template |
| group.Root = "/" + grouplessPrefix |
| group.GroupVersion = grouplessGroupVersion |
| group.OptionsExternalVersion = &grouplessGroupVersion |
| group.Serializer = codecs |
| if err := (&group).InstallREST(container); err != nil { |
| panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) |
| } |
| } |
| |
| // group version 1 |
| { |
| group := template |
| group.Root = "/" + prefix |
| group.GroupVersion = testGroupVersion |
| group.OptionsExternalVersion = &testGroupVersion |
| group.Serializer = codecs |
| if err := (&group).InstallREST(container); err != nil { |
| panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) |
| } |
| } |
| |
| // group version 2 |
| { |
| group := template |
| group.Root = "/" + prefix |
| group.GroupVersion = newGroupVersion |
| group.OptionsExternalVersion = &newGroupVersion |
| group.Serializer = codecs |
| if err := (&group).InstallREST(container); err != nil { |
| panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) |
| } |
| } |
| handler := genericapifilters.WithAudit(mux, auditSink, auditpolicy.FakeChecker(auditinternal.LevelRequestResponse, nil), func(r *http.Request, requestInfo *request.RequestInfo) bool { |
| // simplified long-running check |
| return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy" |
| }) |
| handler = genericapifilters.WithRequestInfo(handler, testRequestInfoResolver()) |
| |
| return &defaultAPIServer{handler, container} |
| } |
| |
| func testRequestInfoResolver() *request.RequestInfoFactory { |
| return &request.RequestInfoFactory{ |
| APIPrefixes: sets.NewString("api", "apis"), |
| GrouplessAPIPrefixes: sets.NewString("api"), |
| } |
| } |
| |
| func TestSimpleSetupRight(t *testing.T) { |
| s := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "aName"}} |
| wire, err := runtime.Encode(codec, s) |
| if err != nil { |
| t.Fatal(err) |
| } |
| s2, err := runtime.Decode(codec, wire) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(s, s2) { |
| t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) |
| } |
| } |
| |
| func TestSimpleOptionsSetupRight(t *testing.T) { |
| s := &genericapitesting.SimpleGetOptions{} |
| wire, err := runtime.Encode(codec, s) |
| if err != nil { |
| t.Fatal(err) |
| } |
| s2, err := runtime.Decode(codec, wire) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(s, s2) { |
| t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) |
| } |
| } |
| |
| type SimpleRESTStorage struct { |
| lock sync.Mutex |
| |
| errors map[string]error |
| list []genericapitesting.Simple |
| item genericapitesting.Simple |
| |
| updated *genericapitesting.Simple |
| created *genericapitesting.Simple |
| |
| stream *SimpleStream |
| |
| deleted string |
| deleteOptions *metav1.DeleteOptions |
| |
| actualNamespace string |
| namespacePresent bool |
| |
| // These are set when Watch is called |
| fakeWatch *watch.FakeWatcher |
| requestedLabelSelector labels.Selector |
| requestedFieldSelector fields.Selector |
| requestedUninitialized bool |
| requestedResourceVersion string |
| requestedResourceNamespace string |
| |
| // The id requested, and location to return for ResourceLocation |
| requestedResourceLocationID string |
| resourceLocation *url.URL |
| resourceLocationTransport http.RoundTripper |
| expectedResourceNamespace string |
| |
| // If non-nil, called inside the WorkFunc when answering update, delete, create. |
| // obj receives the original input to the update, delete, or create call. |
| injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error) |
| } |
| |
| func (storage *SimpleRESTStorage) NamespaceScoped() bool { |
| return true |
| } |
| |
| func (storage *SimpleRESTStorage) Export(ctx context.Context, name string, opts metav1.ExportOptions) (runtime.Object, error) { |
| obj, err := storage.Get(ctx, name, &metav1.GetOptions{}) |
| if err != nil { |
| return nil, err |
| } |
| s, ok := obj.(*genericapitesting.Simple) |
| if !ok { |
| return nil, fmt.Errorf("unexpected object") |
| } |
| |
| // Set a marker to verify the method was called |
| s.Other = "exported" |
| return obj, storage.errors["export"] |
| } |
| |
| func (storage *SimpleRESTStorage) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) { |
| return rest.NewDefaultTableConvertor(schema.GroupResource{Resource: "simple"}).ConvertToTable(ctx, obj, tableOptions) |
| } |
| |
| func (storage *SimpleRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| result := &genericapitesting.SimpleList{ |
| ListMeta: metav1.ListMeta{ |
| ResourceVersion: "10", |
| SelfLink: "/test/link", |
| }, |
| Items: storage.list, |
| } |
| storage.requestedLabelSelector = labels.Everything() |
| if options != nil && options.LabelSelector != nil { |
| storage.requestedLabelSelector = options.LabelSelector |
| } |
| storage.requestedFieldSelector = fields.Everything() |
| if options != nil && options.FieldSelector != nil { |
| storage.requestedFieldSelector = options.FieldSelector |
| } |
| storage.requestedUninitialized = options.IncludeUninitialized |
| return result, storage.errors["list"] |
| } |
| |
| type SimpleStream struct { |
| version string |
| accept string |
| contentType string |
| err error |
| |
| io.Reader |
| closed bool |
| } |
| |
| func (s *SimpleStream) Close() error { |
| s.closed = true |
| return nil |
| } |
| |
| func (obj *SimpleStream) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } |
| func (obj *SimpleStream) DeepCopyObject() runtime.Object { |
| panic("SimpleStream does not support DeepCopy") |
| } |
| |
| func (s *SimpleStream) InputStream(_ context.Context, version, accept string) (io.ReadCloser, bool, string, error) { |
| s.version = version |
| s.accept = accept |
| return s, false, s.contentType, s.err |
| } |
| |
| type OutputConnect struct { |
| response string |
| } |
| |
| func (h *OutputConnect) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
| w.Write([]byte(h.response)) |
| } |
| |
| func (storage *SimpleRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| if id == "binary" { |
| return storage.stream, storage.errors["get"] |
| } |
| return storage.item.DeepCopy(), storage.errors["get"] |
| } |
| |
| func (storage *SimpleRESTStorage) checkContext(ctx context.Context) { |
| storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx) |
| } |
| |
| func (storage *SimpleRESTStorage) Delete(ctx context.Context, id string, options *metav1.DeleteOptions) (runtime.Object, bool, error) { |
| storage.checkContext(ctx) |
| storage.deleted = id |
| storage.deleteOptions = options |
| if err := storage.errors["delete"]; err != nil { |
| return nil, false, err |
| } |
| var obj runtime.Object = &metav1.Status{Status: metav1.StatusSuccess} |
| var err error |
| if storage.injectedFunction != nil { |
| obj, err = storage.injectedFunction(&genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: id}}) |
| } |
| return obj, true, err |
| } |
| |
| func (storage *SimpleRESTStorage) New() runtime.Object { |
| return &genericapitesting.Simple{} |
| } |
| |
| func (storage *SimpleRESTStorage) NewList() runtime.Object { |
| return &genericapitesting.SimpleList{} |
| } |
| |
| func (storage *SimpleRESTStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| storage.created = obj.(*genericapitesting.Simple) |
| if err := storage.errors["create"]; err != nil { |
| return nil, err |
| } |
| var err error |
| if storage.injectedFunction != nil { |
| obj, err = storage.injectedFunction(obj) |
| } |
| if err := createValidation(obj); err != nil { |
| return nil, err |
| } |
| return obj, err |
| } |
| |
| func (storage *SimpleRESTStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { |
| storage.checkContext(ctx) |
| obj, err := objInfo.UpdatedObject(ctx, &storage.item) |
| if err != nil { |
| return nil, false, err |
| } |
| storage.updated = obj.(*genericapitesting.Simple) |
| if err := storage.errors["update"]; err != nil { |
| return nil, false, err |
| } |
| if storage.injectedFunction != nil { |
| obj, err = storage.injectedFunction(obj) |
| } |
| if err := updateValidation(&storage.item, obj); err != nil { |
| return nil, false, err |
| } |
| return obj, false, err |
| } |
| |
| // Implement ResourceWatcher. |
| func (storage *SimpleRESTStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { |
| storage.lock.Lock() |
| defer storage.lock.Unlock() |
| storage.checkContext(ctx) |
| storage.requestedLabelSelector = labels.Everything() |
| if options != nil && options.LabelSelector != nil { |
| storage.requestedLabelSelector = options.LabelSelector |
| } |
| storage.requestedFieldSelector = fields.Everything() |
| if options != nil && options.FieldSelector != nil { |
| storage.requestedFieldSelector = options.FieldSelector |
| } |
| storage.requestedResourceVersion = "" |
| if options != nil { |
| storage.requestedResourceVersion = options.ResourceVersion |
| } |
| storage.requestedResourceNamespace = request.NamespaceValue(ctx) |
| if err := storage.errors["watch"]; err != nil { |
| return nil, err |
| } |
| storage.fakeWatch = watch.NewFake() |
| return storage.fakeWatch, nil |
| } |
| |
| func (storage *SimpleRESTStorage) Watcher() *watch.FakeWatcher { |
| storage.lock.Lock() |
| defer storage.lock.Unlock() |
| return storage.fakeWatch |
| } |
| |
| // Implement Connecter |
| type ConnecterRESTStorage struct { |
| connectHandler http.Handler |
| handlerFunc func() http.Handler |
| |
| emptyConnectOptions runtime.Object |
| receivedConnectOptions runtime.Object |
| receivedID string |
| receivedResponder rest.Responder |
| takesPath string |
| } |
| |
| // Implement Connecter |
| var _ = rest.Connecter(&ConnecterRESTStorage{}) |
| |
| func (s *ConnecterRESTStorage) New() runtime.Object { |
| return &genericapitesting.Simple{} |
| } |
| |
| func (s *ConnecterRESTStorage) Connect(ctx context.Context, id string, options runtime.Object, responder rest.Responder) (http.Handler, error) { |
| s.receivedConnectOptions = options |
| s.receivedID = id |
| s.receivedResponder = responder |
| if s.handlerFunc != nil { |
| return s.handlerFunc(), nil |
| } |
| return s.connectHandler, nil |
| } |
| |
| func (s *ConnecterRESTStorage) ConnectMethods() []string { |
| return []string{"GET", "POST", "PUT", "DELETE"} |
| } |
| |
| func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string) { |
| if len(s.takesPath) > 0 { |
| return s.emptyConnectOptions, true, s.takesPath |
| } |
| return s.emptyConnectOptions, false, "" |
| } |
| |
| type MetadataRESTStorage struct { |
| *SimpleRESTStorage |
| types []string |
| } |
| |
| func (m *MetadataRESTStorage) ProducesMIMETypes(method string) []string { |
| return m.types |
| } |
| |
| func (m *MetadataRESTStorage) ProducesObject(verb string) interface{} { |
| return nil |
| } |
| |
| var _ rest.StorageMetadata = &MetadataRESTStorage{} |
| |
| type GetWithOptionsRESTStorage struct { |
| *SimpleRESTStorage |
| optionsReceived runtime.Object |
| takesPath string |
| } |
| |
| func (r *GetWithOptionsRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { |
| if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok { |
| return nil, fmt.Errorf("Unexpected options object: %#v", options) |
| } |
| r.optionsReceived = options |
| return r.SimpleRESTStorage.Get(ctx, name, &metav1.GetOptions{}) |
| } |
| |
| func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, string) { |
| if len(r.takesPath) > 0 { |
| return &genericapitesting.SimpleGetOptions{}, true, r.takesPath |
| } |
| return &genericapitesting.SimpleGetOptions{}, false, "" |
| } |
| |
| var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{} |
| |
| type GetWithOptionsRootRESTStorage struct { |
| *SimpleTypedStorage |
| optionsReceived runtime.Object |
| takesPath string |
| } |
| |
| func (r *GetWithOptionsRootRESTStorage) NamespaceScoped() bool { |
| return false |
| } |
| |
| func (r *GetWithOptionsRootRESTStorage) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { |
| if _, ok := options.(*genericapitesting.SimpleGetOptions); !ok { |
| return nil, fmt.Errorf("Unexpected options object: %#v", options) |
| } |
| r.optionsReceived = options |
| return r.SimpleTypedStorage.Get(ctx, name, &metav1.GetOptions{}) |
| } |
| |
| func (r *GetWithOptionsRootRESTStorage) NewGetOptions() (runtime.Object, bool, string) { |
| if len(r.takesPath) > 0 { |
| return &genericapitesting.SimpleGetOptions{}, true, r.takesPath |
| } |
| return &genericapitesting.SimpleGetOptions{}, false, "" |
| } |
| |
| var _ rest.GetterWithOptions = &GetWithOptionsRootRESTStorage{} |
| |
| type NamedCreaterRESTStorage struct { |
| *SimpleRESTStorage |
| createdName string |
| } |
| |
| func (storage *NamedCreaterRESTStorage) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| storage.created = obj.(*genericapitesting.Simple) |
| storage.createdName = name |
| if err := storage.errors["create"]; err != nil { |
| return nil, err |
| } |
| var err error |
| if storage.injectedFunction != nil { |
| obj, err = storage.injectedFunction(obj) |
| } |
| if err := createValidation(obj); err != nil { |
| return nil, err |
| } |
| return obj, err |
| } |
| |
| type SimpleTypedStorage struct { |
| errors map[string]error |
| item runtime.Object |
| baseType runtime.Object |
| |
| actualNamespace string |
| namespacePresent bool |
| } |
| |
| func (storage *SimpleTypedStorage) New() runtime.Object { |
| return storage.baseType |
| } |
| |
| func (storage *SimpleTypedStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| return storage.item.DeepCopyObject(), storage.errors["get"] |
| } |
| |
| func (storage *SimpleTypedStorage) checkContext(ctx context.Context) { |
| storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx) |
| } |
| |
| func bodyOrDie(response *http.Response) string { |
| defer response.Body.Close() |
| body, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| panic(err) |
| } |
| return string(body) |
| } |
| |
| func extractBody(response *http.Response, object runtime.Object) (string, error) { |
| return extractBodyDecoder(response, object, codec) |
| } |
| |
| func extractBodyDecoder(response *http.Response, object runtime.Object, decoder runtime.Decoder) (string, error) { |
| defer response.Body.Close() |
| body, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| return string(body), err |
| } |
| return string(body), runtime.DecodeInto(decoder, body, object) |
| } |
| |
| func extractBodyObject(response *http.Response, decoder runtime.Decoder) (runtime.Object, string, error) { |
| defer response.Body.Close() |
| body, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| return nil, string(body), err |
| } |
| obj, err := runtime.Decode(decoder, body) |
| return obj, string(body), err |
| } |
| |
| func TestNotFound(t *testing.T) { |
| type T struct { |
| Method string |
| Path string |
| Status int |
| } |
| cases := map[string]T{ |
| // Positive checks to make sure everything is wired correctly |
| "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK}, |
| "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, |
| |
| "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, |
| |
| "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, |
| "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError}, |
| |
| "groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, |
| "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, |
| "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, |
| "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusInternalServerError}, |
| "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, |
| "groupless namespaced watch param with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, |
| |
| // Positive checks to make sure everything is wired correctly |
| "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, |
| // TODO: JTL: "GET root item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK}, |
| "GET namespaced": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, |
| // TODO: JTL: "GET namespaced item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusOK}, |
| |
| "GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, |
| |
| "root PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "root GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, |
| "root GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| // TODO: JTL: "root POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusMethodNotAllowed}, |
| "root DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "root DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| "root PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, |
| "root PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, |
| "root watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError}, |
| // TODO: JTL: "root watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroot/bar", http.StatusMethodNotAllowed}, |
| |
| "namespaced PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "namespaced GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, |
| "namespaced GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, |
| "namespaced GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "namespaced POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, |
| "namespaced DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "namespaced DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "namespaced PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, |
| "namespaced PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, |
| "namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusInternalServerError}, |
| "namespaced watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, |
| "namespaced watch param with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, |
| } |
| handler := handle(map[string]rest.Storage{ |
| "simples": &SimpleRESTStorage{}, |
| "simpleroots": &SimpleRESTStorage{}, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| for k, v := range cases { |
| request, err := http.NewRequest(v.Method, server.URL+v.Path, nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if response.StatusCode != v.Status { |
| t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response) |
| } |
| } |
| } |
| |
| type UnimplementedRESTStorage struct{} |
| |
| func (UnimplementedRESTStorage) NamespaceScoped() bool { |
| return true |
| } |
| |
| func (UnimplementedRESTStorage) New() runtime.Object { |
| return &genericapitesting.Simple{} |
| } |
| |
| // TestUnimplementedRESTStorage ensures that if a rest.Storage does not implement a given |
| // method, that it is literally not registered with the server. In the past, |
| // we registered everything, and returned method not supported if it didn't support |
| // a verb. Now we literally do not register a storage if it does not implement anything. |
| // TODO: in future, we should update proxy/redirect |
| func TestUnimplementedRESTStorage(t *testing.T) { |
| type T struct { |
| Method string |
| Path string |
| ErrCode int |
| } |
| cases := map[string]T{ |
| "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, |
| "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, |
| "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound}, |
| "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, |
| "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, |
| |
| "GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, |
| "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, |
| "PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, |
| "watch list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo", http.StatusNotFound}, |
| "watch object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, |
| "proxy object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, |
| } |
| handler := handle(map[string]rest.Storage{ |
| "foo": UnimplementedRESTStorage{}, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| for k, v := range cases { |
| request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`))) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| response, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| defer response.Body.Close() |
| data, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if response.StatusCode != v.ErrCode { |
| t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data)) |
| continue |
| } |
| } |
| } |
| |
| type OnlyGetRESTStorage struct { |
| UnimplementedRESTStorage |
| } |
| |
| func (OnlyGetRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { |
| return nil, nil |
| } |
| |
| func (OnlyGetRESTStorage) NewList() runtime.Object { |
| return &genericapitesting.SimpleList{} |
| } |
| |
| func (OnlyGetRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { |
| return nil, nil |
| } |
| |
| // TestSomeUnimplementedRESTStorage ensures that if a rest.Storage does |
| // not implement a given method, that it is literally not registered |
| // with the server. We need to have at least one verb supported inorder |
| // to get a MethodNotAllowed rather than NotFound error. |
| func TestSomeUnimplementedRESTStorage(t *testing.T) { |
| type T struct { |
| Method string |
| Path string |
| ErrCode int |
| } |
| |
| cases := map[string]T{ |
| "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, |
| "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, |
| "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, |
| "groupless DELETE collection": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, |
| "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, |
| "PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, |
| "DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed}, |
| "DELETE collection": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed}, |
| } |
| handler := handle(map[string]rest.Storage{ |
| "foo": OnlyGetRESTStorage{}, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| for k, v := range cases { |
| request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`))) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| response, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| defer response.Body.Close() |
| data, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if response.StatusCode != v.ErrCode { |
| t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data)) |
| continue |
| } |
| } |
| } |
| |
| func TestList(t *testing.T) { |
| testCases := []struct { |
| url string |
| namespace string |
| selfLink string |
| legacy bool |
| label string |
| field string |
| }{ |
| // Groupless API |
| |
| // legacy namespace param is ignored |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| legacy: true, |
| label: "a=b", |
| field: "c=d", |
| }, |
| // legacy api version is honored |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| namespace: "other", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "other", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| legacy: true, |
| label: "a=b", |
| field: "c=d", |
| }, |
| // list items across all namespaces |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| // list items in a namespace in the path |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| namespace: "default", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| namespace: "other", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "other", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", |
| label: "a=b", |
| field: "c=d", |
| }, |
| // list items across all namespaces |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", |
| }, |
| |
| // Group API |
| |
| // legacy namespace param is ignored |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| legacy: true, |
| label: "a=b", |
| field: "c=d", |
| }, |
| // legacy api version is honored |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", |
| namespace: "other", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", |
| legacy: true, |
| }, |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "other", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", |
| legacy: true, |
| label: "a=b", |
| field: "c=d", |
| }, |
| // list items across all namespaces |
| { |
| url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", |
| legacy: true, |
| }, |
| // list items in a namespace in the path |
| { |
| url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple", |
| namespace: "default", |
| selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple", |
| }, |
| { |
| url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", |
| namespace: "other", |
| selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", |
| }, |
| { |
| url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", |
| namespace: "other", |
| selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", |
| label: "a=b", |
| field: "c=d", |
| }, |
| // list items across all namespaces |
| { |
| url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple", |
| namespace: "", |
| selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple", |
| }, |
| } |
| for i, testCase := range testCases { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace} |
| storage["simple"] = &simpleStorage |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| namespace: testCase.namespace, |
| expectedSet: testCase.selfLink, |
| } |
| var handler = handleInternal(storage, admissionControl, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + testCase.url) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| defer resp.Body.Close() |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| t.Logf("%d: body: %s", i, string(body)) |
| continue |
| } |
| // TODO: future, restore get links |
| if !selfLinker.called { |
| t.Errorf("%d: never set self link", i) |
| } |
| if !simpleStorage.namespacePresent { |
| t.Errorf("%d: namespace not set", i) |
| } else if simpleStorage.actualNamespace != testCase.namespace { |
| t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace) |
| } |
| if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label { |
| t.Errorf("%d: unexpected label selector: expected=%v got=%v", i, testCase.label, simpleStorage.requestedLabelSelector) |
| } |
| if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field { |
| t.Errorf("%d: unexpected field selector: expected=%v got=%v", i, testCase.field, simpleStorage.requestedFieldSelector) |
| } |
| } |
| } |
| |
| func TestRequestsWithInvalidQuery(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| |
| storage["simple"] = &SimpleRESTStorage{expectedResourceNamespace: "default"} |
| storage["withoptions"] = GetWithOptionsRESTStorage{} |
| |
| var handler = handleInternal(storage, admissionControl, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| for i, test := range []struct { |
| postfix string |
| method string |
| }{ |
| {"/simple?labelSelector=<invalid>", http.MethodGet}, |
| {"/simple/foo?gracePeriodSeconds=<invalid>", http.MethodDelete}, |
| // {"/simple?labelSelector=<value>", http.MethodDelete}, TODO: implement DeleteCollection in SimpleRESTStorage |
| // {"/simple/foo?export=<invalid>", http.MethodGet}, TODO: there is no invalid bool in conversion. Should we be more strict? |
| // {"/simple/foo?resourceVersion=<invalid>", http.MethodGet}, TODO: there is no invalid resourceVersion. Should we be more strict? |
| // {"/withoptions?labelSelector=<invalid>", http.MethodGet}, TODO: SimpleGetOptions is always valid. Add more validation that can fail. |
| } { |
| baseURL := server.URL + "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default" |
| url := baseURL + test.postfix |
| r, err := http.NewRequest(test.method, url, nil) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| resp, err := http.DefaultClient.Do(r) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| defer resp.Body.Close() |
| if resp.StatusCode != http.StatusBadRequest { |
| t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, url, http.StatusBadRequest, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| t.Logf("%d: body: %s", i, string(body)) |
| } |
| } |
| } |
| |
| func TestListCompression(t *testing.T) { |
| testCases := []struct { |
| url string |
| namespace string |
| selfLink string |
| legacy bool |
| label string |
| field string |
| acceptEncoding string |
| }{ |
| // list items in a namespace in the path |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| namespace: "default", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| acceptEncoding: "", |
| }, |
| { |
| url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| namespace: "default", |
| selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", |
| acceptEncoding: "gzip", |
| }, |
| } |
| for i, testCase := range testCases { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace} |
| storage["simple"] = &simpleStorage |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| namespace: testCase.namespace, |
| expectedSet: testCase.selfLink, |
| } |
| var handler = handleInternal(storage, admissionControl, selfLinker, nil) |
| |
| handler = filters.WithCompression(handler) |
| handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) |
| |
| server := httptest.NewServer(handler) |
| |
| defer server.Close() |
| |
| req, err := http.NewRequest("GET", server.URL+testCase.url, nil) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| // It's necessary to manually set Accept-Encoding here |
| // to prevent http.DefaultClient from automatically |
| // decoding responses |
| req.Header.Set("Accept-Encoding", testCase.acceptEncoding) |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| defer resp.Body.Close() |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Errorf("%d: unexpected error: %v", i, err) |
| continue |
| } |
| t.Logf("%d: body: %s", i, string(body)) |
| continue |
| } |
| // TODO: future, restore get links |
| if !selfLinker.called { |
| t.Errorf("%d: never set self link", i) |
| } |
| if !simpleStorage.namespacePresent { |
| t.Errorf("%d: namespace not set", i) |
| } else if simpleStorage.actualNamespace != testCase.namespace { |
| t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace) |
| } |
| if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label { |
| t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector) |
| } |
| if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field { |
| t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector) |
| } |
| |
| var decoder *json.Decoder |
| if testCase.acceptEncoding == "gzip" { |
| gzipReader, err := gzip.NewReader(resp.Body) |
| if err != nil { |
| t.Fatalf("unexpected error creating gzip reader: %v", err) |
| } |
| decoder = json.NewDecoder(gzipReader) |
| } else { |
| decoder = json.NewDecoder(resp.Body) |
| } |
| var itemOut genericapitesting.SimpleList |
| err = decoder.Decode(&itemOut) |
| if err != nil { |
| t.Errorf("failed to read response body as SimpleList: %v", err) |
| } |
| } |
| } |
| |
| func TestLogs(t *testing.T) { |
| handler := handle(map[string]rest.Storage{}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| request, err := http.NewRequest("GET", server.URL+"/logs", nil) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| body, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf("Data: %s", string(body)) |
| } |
| |
| func TestErrorList(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| errors: map[string]error{"list": fmt.Errorf("test Error")}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusInternalServerError { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp) |
| } |
| } |
| |
| func TestNonEmptyList(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| list: []genericapitesting.Simple{ |
| { |
| ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "other"}, |
| Other: "foo", |
| }, |
| }, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf("Data: %s", string(body)) |
| } |
| |
| var listOut genericapitesting.SimpleList |
| body, err := extractBody(resp, &listOut) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Log(body) |
| |
| if len(listOut.Items) != 1 { |
| t.Errorf("Unexpected response: %#v", listOut) |
| return |
| } |
| if listOut.Items[0].Other != simpleStorage.list[0].Other { |
| t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) |
| } |
| if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" { |
| t.Errorf("unexpected list self link: %#v", listOut) |
| } |
| expectedSelfLink := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple/something" |
| if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink { |
| t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink) |
| } |
| } |
| |
| func TestSelfLinkSkipsEmptyName(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| list: []genericapitesting.Simple{ |
| { |
| ObjectMeta: metav1.ObjectMeta{Namespace: "other"}, |
| Other: "foo", |
| }, |
| }, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf("Data: %s", string(body)) |
| } |
| var listOut genericapitesting.SimpleList |
| body, err := extractBody(resp, &listOut) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if len(listOut.Items) != 1 { |
| t.Errorf("Unexpected response: %#v", listOut) |
| return |
| } |
| if listOut.Items[0].Other != simpleStorage.list[0].Other { |
| t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) |
| } |
| if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" { |
| t.Errorf("unexpected list self link: %#v", listOut) |
| } |
| expectedSelfLink := "" |
| if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink { |
| t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink) |
| } |
| } |
| |
| func TestRootSelfLink(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := GetWithOptionsRootRESTStorage{ |
| SimpleTypedStorage: &SimpleTypedStorage{ |
| baseType: &genericapitesting.SimpleRoot{}, // a root scoped type |
| item: &genericapitesting.SimpleRoot{ |
| ObjectMeta: metav1.ObjectMeta{Name: "foo"}, |
| Other: "foo", |
| }, |
| }, |
| takesPath: "atAPath", |
| } |
| storage["simple"] = &simpleStorage |
| storage["simple/sub"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| testCases := []struct { |
| url string |
| selfLink string |
| }{ |
| { |
| url: server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo", |
| }, |
| { |
| url: server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo/sub", |
| selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/foo/sub", |
| }, |
| } |
| |
| for _, test := range testCases { |
| resp, err := http.Get(test.url) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf("Data: %s", string(body)) |
| } |
| var out genericapitesting.SimpleRoot |
| if _, err := extractBody(resp, &out); err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| if out.SelfLink != test.selfLink { |
| t.Errorf("unexpected self link: %#v", out.SelfLink) |
| } |
| } |
| } |
| |
| func TestMetadata(t *testing.T) { |
| simpleStorage := &MetadataRESTStorage{&SimpleRESTStorage{}, []string{"text/plain"}} |
| h := handle(map[string]rest.Storage{"simple": simpleStorage}) |
| ws := h.(*defaultAPIServer).container.RegisteredWebServices() |
| if len(ws) == 0 { |
| t.Fatal("no web services registered") |
| } |
| matches := map[string]int{} |
| for _, w := range ws { |
| for _, r := range w.Routes() { |
| t.Logf("%v %v %#v", r.Method, r.Path, r.Produces) |
| s := strings.Join(r.Produces, ",") |
| i := matches[s] |
| matches[s] = i + 1 |
| } |
| } |
| cs := []func() bool{ |
| func() bool { |
| return matches["text/plain,application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 |
| }, |
| func() bool { |
| return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf,application/json;stream=watch,application/vnd.kubernetes.protobuf;stream=watch"] == 0 |
| }, |
| func() bool { |
| return matches["application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 |
| }, |
| func() bool { |
| return len(matches) != 4 |
| }, |
| } |
| for i, c := range cs { |
| if c() { |
| t.Errorf("[%d]unexpected mime types: %#v", i, matches) |
| } |
| } |
| } |
| |
| func TestExport(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| ResourceVersion: "1234", |
| CreationTimestamp: metav1.NewTime(time.Unix(10, 10)), |
| }, |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| name: "id", |
| namespace: "default", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id?export=true") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| data, _ := ioutil.ReadAll(resp.Body) |
| resp.Body.Close() |
| t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data)) |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if itemOut.Other != "exported" { |
| t.Errorf("Expected: exported, saw: %s", itemOut.Other) |
| } |
| |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestGet(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| name: "id", |
| namespace: "default", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestGetCompression(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| name: "id", |
| namespace: "default", |
| } |
| |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| handler = filters.WithCompression(handler) |
| handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| tests := []struct { |
| acceptEncoding string |
| }{ |
| {acceptEncoding: ""}, |
| {acceptEncoding: "gzip"}, |
| } |
| |
| for _, test := range tests { |
| req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", nil) |
| if err != nil { |
| t.Fatalf("unexpected error cretaing request: %v", err) |
| } |
| // It's necessary to manually set Accept-Encoding here |
| // to prevent http.DefaultClient from automatically |
| // decoding responses |
| req.Header.Set("Accept-Encoding", test.acceptEncoding) |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var decoder *json.Decoder |
| if test.acceptEncoding == "gzip" { |
| gzipReader, err := gzip.NewReader(resp.Body) |
| if err != nil { |
| t.Fatalf("unexpected error creating gzip reader: %v", err) |
| } |
| decoder = json.NewDecoder(gzipReader) |
| } else { |
| decoder = json.NewDecoder(resp.Body) |
| } |
| var itemOut genericapitesting.Simple |
| err = decoder.Decode(&itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Errorf("unexpected error reading body: %v", err) |
| } |
| |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| } |
| |
| func TestGetUninitialized(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| list: []genericapitesting.Simple{ |
| { |
| ObjectMeta: metav1.ObjectMeta{ |
| Initializers: &metav1.Initializers{ |
| Pending: []metav1.Initializer{{Name: "test"}}, |
| }, |
| }, |
| Other: "foo", |
| }, |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple"), |
| name: "id", |
| namespace: "default", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple?includeUninitialized=true") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var itemOut genericapitesting.SimpleList |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if len(itemOut.Items) != 1 || itemOut.Items[0].Other != "foo" { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if !simpleStorage.requestedUninitialized { |
| t.Errorf("Didn't set correct flag") |
| } |
| } |
| |
| func TestGetPretty(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| name: "id", |
| namespace: "default", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| tests := []struct { |
| accept string |
| userAgent string |
| params url.Values |
| pretty bool |
| }{ |
| {accept: runtime.ContentTypeJSON}, |
| {accept: runtime.ContentTypeJSON + ";pretty=0"}, |
| {accept: runtime.ContentTypeJSON, userAgent: "kubectl"}, |
| {accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}}, |
| |
| {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"}, |
| {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"}, |
| {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"}, |
| {pretty: true, accept: runtime.ContentTypeJSON + ";pretty=1"}, |
| {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}}, |
| {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}}, |
| } |
| for i, test := range tests { |
| u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") |
| if err != nil { |
| t.Fatal(err) |
| } |
| u.RawQuery = test.params.Encode() |
| req := &http.Request{Method: "GET", URL: u} |
| req.Header = http.Header{} |
| req.Header.Set("Accept", test.accept) |
| req.Header.Set("User-Agent", test.userAgent) |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatal(err) |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // to get stable ordering we need to use a go type |
| unstructured := genericapitesting.Simple{} |
| if err := json.Unmarshal([]byte(body), &unstructured); err != nil { |
| t.Fatal(err) |
| } |
| var expect string |
| if test.pretty { |
| out, err := json.MarshalIndent(unstructured, "", " ") |
| if err != nil { |
| t.Fatal(err) |
| } |
| expect = string(out) |
| } else { |
| out, err := json.Marshal(unstructured) |
| if err != nil { |
| t.Fatal(err) |
| } |
| expect = string(out) + "\n" |
| } |
| if expect != body { |
| t.Errorf("%d: body did not match expected:\n%s\n%s", i, body, expect) |
| } |
| } |
| } |
| |
| func TestGetTable(t *testing.T) { |
| now := metav1.Now() |
| obj := genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")}, |
| Other: "foo", |
| } |
| |
| m, err := meta.Accessor(&obj) |
| if err != nil { |
| t.Fatal(err) |
| } |
| partial := meta.AsPartialObjectMetadata(m) |
| partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) |
| encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // the codec includes a trailing newline that is not present during decode |
| encodedBody = bytes.TrimSpace(encodedBody) |
| |
| metaDoc := metav1.ObjectMeta{}.SwaggerDoc() |
| |
| tests := []struct { |
| accept string |
| params url.Values |
| pretty bool |
| expected *metav1beta1.Table |
| statusCode int |
| item bool |
| }{ |
| { |
| accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io", |
| statusCode: http.StatusNotAcceptable, |
| }, |
| { |
| item: true, |
| accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", |
| expected: &metav1beta1.Table{ |
| TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, |
| ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, |
| ColumnDefinitions: []metav1beta1.TableColumnDefinition{ |
| {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, |
| {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, |
| }, |
| Rows: []metav1beta1.TableRow{ |
| {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, |
| }, |
| }, |
| }, |
| { |
| item: true, |
| accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", |
| params: url.Values{"includeObject": []string{"Metadata"}}, |
| expected: &metav1beta1.Table{ |
| TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, |
| ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, |
| ColumnDefinitions: []metav1beta1.TableColumnDefinition{ |
| {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, |
| {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, |
| }, |
| Rows: []metav1beta1.TableRow{ |
| {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, |
| }, |
| }, |
| }, |
| { |
| accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", |
| params: url.Values{"includeObject": []string{"Metadata"}}, |
| expected: &metav1beta1.Table{ |
| TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, |
| ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"}, |
| ColumnDefinitions: []metav1beta1.TableColumnDefinition{ |
| {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, |
| {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, |
| }, |
| Rows: []metav1beta1.TableRow{ |
| {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, |
| }, |
| }, |
| }, |
| } |
| for i, test := range tests { |
| t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: obj, |
| list: []genericapitesting.Simple{obj}, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple", |
| namespace: "default", |
| } |
| if test.item { |
| selfLinker.expectedSet += "/id" |
| selfLinker.name = "id" |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| var id string |
| if test.item { |
| id = "/id" |
| } |
| u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id) |
| if err != nil { |
| t.Fatal(err) |
| } |
| u.RawQuery = test.params.Encode() |
| req := &http.Request{Method: "GET", URL: u} |
| req.Header = http.Header{} |
| req.Header.Set("Accept", test.accept) |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if test.statusCode != 0 { |
| if resp.StatusCode != test.statusCode { |
| t.Errorf("%d: unexpected response: %#v", i, resp) |
| } |
| obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) |
| if err != nil { |
| t.Fatalf("%d: unexpected body read error: %v", i, err) |
| } |
| gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} |
| if obj.GetObjectKind().GroupVersionKind() != gvk { |
| t.Fatalf("%d: unexpected error body: %#v", i, obj) |
| } |
| return |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("%d: unexpected response: %#v", i, resp) |
| } |
| var itemOut metav1beta1.Table |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(test.expected, &itemOut) { |
| t.Log(body) |
| t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut)) |
| } |
| }) |
| } |
| } |
| |
| func TestGetPartialObjectMetadata(t *testing.T) { |
| now := metav1.Time{metav1.Now().Rfc3339Copy().Local()} |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, |
| Other: "foo", |
| }, |
| list: []genericapitesting.Simple{ |
| { |
| ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")}, |
| Other: "foo", |
| }, |
| { |
| ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")}, |
| Other: "bar", |
| }, |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", |
| alternativeSet: sets.NewString("/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple"), |
| name: "id", |
| namespace: "default", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| tests := []struct { |
| accept string |
| params url.Values |
| pretty bool |
| list bool |
| expected runtime.Object |
| expectKind schema.GroupVersionKind |
| statusCode int |
| }{ |
| { |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io", |
| statusCode: http.StatusNotAcceptable, |
| }, |
| { |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", |
| expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, |
| }, |
| { |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", |
| expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, |
| }, |
| { |
| list: true, |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", |
| statusCode: http.StatusNotAcceptable, |
| }, |
| { |
| list: true, |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", |
| expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, |
| }, |
| { |
| list: true, |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json", |
| expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"}, |
| }, |
| { |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", |
| statusCode: http.StatusNotAcceptable, |
| }, |
| { |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", |
| expected: &metav1beta1.PartialObjectMetadata{ |
| ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, |
| }, |
| expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, |
| }, |
| { |
| list: true, |
| accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", |
| expected: &metav1beta1.PartialObjectMetadataList{ |
| Items: []*metav1beta1.PartialObjectMetadata{ |
| { |
| TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"}, |
| ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("newer")}, |
| }, |
| { |
| TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "PartialObjectMetadata"}, |
| ObjectMeta: metav1.ObjectMeta{Name: "foo2", Namespace: "ns2", CreationTimestamp: now, UID: types.UID("older")}, |
| }, |
| }, |
| }, |
| expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"}, |
| }, |
| } |
| for i, test := range tests { |
| suffix := "/namespaces/default/simple/id" |
| if test.list { |
| suffix = "/namespaces/default/simple" |
| } |
| u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + suffix) |
| if err != nil { |
| t.Fatal(err) |
| } |
| u.RawQuery = test.params.Encode() |
| req := &http.Request{Method: "GET", URL: u} |
| req.Header = http.Header{} |
| req.Header.Set("Accept", test.accept) |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if test.statusCode != 0 { |
| if resp.StatusCode != test.statusCode { |
| t.Errorf("%d: unexpected response: %#v", i, resp) |
| } |
| obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) |
| if err != nil { |
| t.Errorf("%d: unexpected body read error: %v", i, err) |
| continue |
| } |
| gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} |
| if obj.GetObjectKind().GroupVersionKind() != gvk { |
| t.Errorf("%d: unexpected error body: %#v", i, obj) |
| } |
| continue |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("%d: invalid status: %#v\n%s", i, resp, bodyOrDie(resp)) |
| continue |
| } |
| body := "" |
| if test.expected != nil { |
| itemOut, d, err := extractBodyObject(resp, metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(test.expected, itemOut) { |
| t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, itemOut)) |
| } |
| body = d |
| } else { |
| d, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| body = string(d) |
| } |
| obj := &unstructured.Unstructured{} |
| if err := json.Unmarshal([]byte(body), obj); err != nil { |
| t.Fatal(err) |
| } |
| if obj.GetObjectKind().GroupVersionKind() != test.expectKind { |
| t.Errorf("%d: unexpected kind: %#v", i, obj.GetObjectKind().GroupVersionKind()) |
| } |
| } |
| } |
| |
| func TestGetBinary(t *testing.T) { |
| simpleStorage := SimpleRESTStorage{ |
| stream: &SimpleStream{ |
| contentType: "text/plain", |
| Reader: bytes.NewBufferString("response data"), |
| }, |
| } |
| stream := simpleStorage.stream |
| server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage})) |
| defer server.Close() |
| |
| req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/binary", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| req.Header.Add("Accept", "text/other, */*") |
| resp, err := http.DefaultClient.Do(req) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if !stream.closed || stream.version != testGroupVersion.String() || stream.accept != "text/other, */*" || |
| resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" { |
| t.Errorf("unexpected stream: %#v", stream) |
| } |
| } |
| |
| func validateSimpleGetOptionsParams(t *testing.T, route *restful.Route) { |
| // Validate name and description |
| expectedParams := map[string]string{ |
| "param1": "description for param1", |
| "param2": "description for param2", |
| "atAPath": "", |
| } |
| for _, p := range route.ParameterDocs { |
| data := p.Data() |
| if desc, exists := expectedParams[data.Name]; exists { |
| if desc != data.Description { |
| t.Errorf("unexpected description for parameter %s: %s\n", data.Name, data.Description) |
| } |
| delete(expectedParams, data.Name) |
| } |
| } |
| if len(expectedParams) > 0 { |
| t.Errorf("did not find all expected parameters: %#v", expectedParams) |
| } |
| } |
| |
| func TestGetWithOptionsRouteParams(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := GetWithOptionsRESTStorage{ |
| SimpleRESTStorage: &SimpleRESTStorage{}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| ws := handler.(*defaultAPIServer).container.RegisteredWebServices() |
| if len(ws) == 0 { |
| t.Fatal("no web services registered") |
| } |
| routes := ws[0].Routes() |
| for i := range routes { |
| if routes[i].Method == "GET" && routes[i].Operation == "readNamespacedSimple" { |
| validateSimpleGetOptionsParams(t, &routes[i]) |
| break |
| } |
| } |
| } |
| |
| func TestGetWithOptions(t *testing.T) { |
| |
| tests := []struct { |
| name string |
| rootScoped bool |
| requestURL string |
| expectedPath string |
| }{ |
| { |
| name: "basic", |
| requestURL: "/namespaces/default/simple/id?param1=test1¶m2=test2", |
| expectedPath: "", |
| }, |
| { |
| name: "with root slash", |
| requestURL: "/namespaces/default/simple/id/?param1=test1¶m2=test2", |
| expectedPath: "/", |
| }, |
| { |
| name: "with path", |
| requestURL: "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2", |
| expectedPath: "/a/different/path", |
| }, |
| { |
| name: "with path with trailing slash", |
| requestURL: "/namespaces/default/simple/id/a/different/path/?param1=test1¶m2=test2", |
| expectedPath: "/a/different/path/", |
| }, |
| { |
| name: "as subresource", |
| requestURL: "/namespaces/default/simple/id/subresource/another/different/path?param1=test1¶m2=test2", |
| expectedPath: "/another/different/path", |
| }, |
| { |
| name: "cluster-scoped basic", |
| rootScoped: true, |
| requestURL: "/simple/id?param1=test1¶m2=test2", |
| expectedPath: "", |
| }, |
| { |
| name: "cluster-scoped basic with path", |
| rootScoped: true, |
| requestURL: "/simple/id/a/cluster/path?param1=test1¶m2=test2", |
| expectedPath: "/a/cluster/path", |
| }, |
| { |
| name: "cluster-scoped basic as subresource", |
| rootScoped: true, |
| requestURL: "/simple/id/subresource/another/cluster/path?param1=test1¶m2=test2", |
| expectedPath: "/another/cluster/path", |
| }, |
| } |
| |
| for _, test := range tests { |
| simpleStorage := GetWithOptionsRESTStorage{ |
| SimpleRESTStorage: &SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| }, |
| takesPath: "atAPath", |
| } |
| simpleRootStorage := GetWithOptionsRootRESTStorage{ |
| SimpleTypedStorage: &SimpleTypedStorage{ |
| baseType: &genericapitesting.SimpleRoot{}, // a root scoped type |
| item: &genericapitesting.SimpleRoot{ |
| Other: "foo", |
| }, |
| }, |
| takesPath: "atAPath", |
| } |
| |
| storage := map[string]rest.Storage{} |
| if test.rootScoped { |
| storage["simple"] = &simpleRootStorage |
| storage["simple/subresource"] = &simpleRootStorage |
| } else { |
| storage["simple"] = &simpleStorage |
| storage["simple/subresource"] = &simpleStorage |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + test.requestURL) |
| if err != nil { |
| t.Errorf("%s: %v", test.name, err) |
| continue |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("%s: unexpected response: %#v", test.name, resp) |
| continue |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Errorf("%s: %v", test.name, err) |
| continue |
| } |
| |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("%s: Unexpected data: %#v, expected %#v (%s)", test.name, itemOut, simpleStorage.item, string(body)) |
| continue |
| } |
| |
| var opts *genericapitesting.SimpleGetOptions |
| var ok bool |
| if test.rootScoped { |
| opts, ok = simpleRootStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) |
| } else { |
| opts, ok = simpleStorage.optionsReceived.(*genericapitesting.SimpleGetOptions) |
| |
| } |
| if !ok { |
| t.Errorf("%s: Unexpected options object received: %#v", test.name, simpleStorage.optionsReceived) |
| continue |
| } |
| if opts.Param1 != "test1" || opts.Param2 != "test2" { |
| t.Errorf("%s: Did not receive expected options: %#v", test.name, opts) |
| continue |
| } |
| if opts.Path != test.expectedPath { |
| t.Errorf("%s: Unexpected path value. Expected: %s. Actual: %s.", test.name, test.expectedPath, opts.Path) |
| continue |
| } |
| } |
| } |
| |
| func TestGetAlternateSelfLink(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id", |
| name: "id", |
| namespace: "test", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestGetNamespaceSelfLink(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| Other: "foo", |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id", |
| name: "id", |
| namespace: "foo", |
| } |
| storage["simple"] = &simpleStorage |
| handler := handleInternal(storage, admissionControl, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if itemOut.Name != simpleStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestGetMissing(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| errors: map[string]error{"get": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusNotFound { |
| t.Errorf("Unexpected response %#v", resp) |
| } |
| } |
| |
| func TestGetRetryAfter(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{ |
| errors: map[string]error{"get": apierrs.NewServerTimeout(schema.GroupResource{Resource: "simples"}, "id", 2)}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusInternalServerError { |
| t.Errorf("Unexpected response %#v", resp) |
| } |
| if resp.Header.Get("Retry-After") != "2" { |
| t.Errorf("Unexpected Retry-After header: %v", resp.Header) |
| } |
| } |
| |
| func TestConnect(t *testing.T) { |
| responseText := "Hello World" |
| itemID := "theID" |
| connectStorage := &ConnecterRESTStorage{ |
| connectHandler: &OutputConnect{ |
| response: responseText, |
| }, |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") |
| |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %#v", resp) |
| } |
| defer resp.Body.Close() |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if connectStorage.receivedID != itemID { |
| t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) |
| } |
| if string(body) != responseText { |
| t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) |
| } |
| } |
| |
| func TestConnectResponderObject(t *testing.T) { |
| itemID := "theID" |
| simple := &genericapitesting.Simple{Other: "foo"} |
| connectStorage := &ConnecterRESTStorage{} |
| connectStorage.handlerFunc = func() http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| connectStorage.receivedResponder.Object(http.StatusCreated, simple) |
| }) |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") |
| |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusCreated { |
| t.Errorf("unexpected response: %#v", resp) |
| } |
| defer resp.Body.Close() |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if connectStorage.receivedID != itemID { |
| t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) |
| } |
| obj, err := runtime.Decode(codec, body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !apiequality.Semantic.DeepEqual(obj, simple) { |
| t.Errorf("Unexpected response: %#v", obj) |
| } |
| } |
| |
| func TestConnectResponderError(t *testing.T) { |
| itemID := "theID" |
| connectStorage := &ConnecterRESTStorage{} |
| connectStorage.handlerFunc = func() http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| connectStorage.receivedResponder.Error(apierrs.NewForbidden(schema.GroupResource{Resource: "simples"}, itemID, errors.New("you are terminated"))) |
| }) |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") |
| |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusForbidden { |
| t.Errorf("unexpected response: %#v", resp) |
| } |
| defer resp.Body.Close() |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if connectStorage.receivedID != itemID { |
| t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) |
| } |
| obj, err := runtime.Decode(codec, body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if obj.(*metav1.Status).Code != http.StatusForbidden { |
| t.Errorf("Unexpected response: %#v", obj) |
| } |
| } |
| |
| func TestConnectWithOptionsRouteParams(t *testing.T) { |
| connectStorage := &ConnecterRESTStorage{ |
| connectHandler: &OutputConnect{}, |
| emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| ws := handler.(*defaultAPIServer).container.RegisteredWebServices() |
| if len(ws) == 0 { |
| t.Fatal("no web services registered") |
| } |
| routes := ws[0].Routes() |
| for i := range routes { |
| switch routes[i].Operation { |
| case "connectGetNamespacedSimpleConnect": |
| case "connectPostNamespacedSimpleConnect": |
| case "connectPutNamespacedSimpleConnect": |
| case "connectDeleteNamespacedSimpleConnect": |
| validateSimpleGetOptionsParams(t, &routes[i]) |
| |
| } |
| } |
| } |
| |
| func TestConnectWithOptions(t *testing.T) { |
| responseText := "Hello World" |
| itemID := "theID" |
| connectStorage := &ConnecterRESTStorage{ |
| connectHandler: &OutputConnect{ |
| response: responseText, |
| }, |
| emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect?param1=value1¶m2=value2") |
| |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %#v", resp) |
| } |
| defer resp.Body.Close() |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if connectStorage.receivedID != itemID { |
| t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) |
| } |
| if string(body) != responseText { |
| t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) |
| } |
| if connectStorage.receivedResponder == nil { |
| t.Errorf("Unexpected responder") |
| } |
| opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions) |
| if !ok { |
| t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) |
| } |
| if opts.Param1 != "value1" && opts.Param2 != "value2" { |
| t.Errorf("Unexpected options value: %#v", opts) |
| } |
| } |
| |
| func TestConnectWithOptionsAndPath(t *testing.T) { |
| responseText := "Hello World" |
| itemID := "theID" |
| testPath := "/a/b/c/def" |
| connectStorage := &ConnecterRESTStorage{ |
| connectHandler: &OutputConnect{ |
| response: responseText, |
| }, |
| emptyConnectOptions: &genericapitesting.SimpleGetOptions{}, |
| takesPath: "atAPath", |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/connect": connectStorage, |
| } |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect" + testPath + "?param1=value1¶m2=value2") |
| |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %#v", resp) |
| } |
| defer resp.Body.Close() |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if connectStorage.receivedID != itemID { |
| t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) |
| } |
| if string(body) != responseText { |
| t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) |
| } |
| opts, ok := connectStorage.receivedConnectOptions.(*genericapitesting.SimpleGetOptions) |
| if !ok { |
| t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) |
| } |
| if opts.Param1 != "value1" && opts.Param2 != "value2" { |
| t.Errorf("Unexpected options value: %#v", opts) |
| } |
| if opts.Path != testPath { |
| t.Errorf("Unexpected path value. Expected: %s. Actual: %s.", testPath, opts.Path) |
| } |
| } |
| |
| func TestDelete(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) |
| res, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if res.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %#v", res) |
| } |
| if simpleStorage.deleted != ID { |
| t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) |
| } |
| } |
| |
| func TestDeleteWithOptions(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| grace := int64(300) |
| item := &metav1.DeleteOptions{ |
| GracePeriodSeconds: &grace, |
| } |
| body, err := runtime.Encode(codec, item) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| res, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if res.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %s %#v", request.URL, res) |
| s, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf(string(s)) |
| } |
| if simpleStorage.deleted != ID { |
| t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) |
| } |
| simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { |
| t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item)) |
| } |
| } |
| |
| func TestDeleteWithOptionsQuery(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| grace := int64(300) |
| item := &metav1.DeleteOptions{ |
| GracePeriodSeconds: &grace, |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace, 10), nil) |
| res, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if res.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %s %#v", request.URL, res) |
| s, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf(string(s)) |
| } |
| if simpleStorage.deleted != ID { |
| t.Fatalf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) |
| } |
| simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { |
| t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item)) |
| } |
| } |
| |
| func TestDeleteWithOptionsQueryAndBody(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| grace := int64(300) |
| item := &metav1.DeleteOptions{ |
| GracePeriodSeconds: &grace, |
| } |
| body, err := runtime.Encode(codec, item) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID+"?gracePeriodSeconds="+strconv.FormatInt(grace+10, 10), bytes.NewReader(body)) |
| res, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if res.StatusCode != http.StatusOK { |
| t.Errorf("unexpected response: %s %#v", request.URL, res) |
| s, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| t.Logf(string(s)) |
| } |
| if simpleStorage.deleted != ID { |
| t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) |
| } |
| simpleStorage.deleteOptions.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !apiequality.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { |
| t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item)) |
| } |
| } |
| |
| func TestDeleteInvokesAdmissionControl(t *testing.T) { |
| // TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins |
| for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { |
| t.Logf("Testing %T", admit) |
| |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handleInternal(storage, admit, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusForbidden { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| } |
| |
| func TestDeleteMissing(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| ID := "id" |
| simpleStorage := SimpleRESTStorage{ |
| errors: map[string]error{"delete": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| client := http.Client{} |
| request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if response.StatusCode != http.StatusNotFound { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| func TestUpdate(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + ID, |
| name: ID, |
| namespace: metav1.NamespaceDefault, |
| } |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| Namespace: "", // update should allow the client to send an empty namespace |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| dump, _ := httputil.DumpResponse(response, true) |
| t.Log(string(dump)) |
| |
| if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name { |
| t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestUpdateInvokesAdmissionControl(t *testing.T) { |
| for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { |
| t.Logf("Testing %T", admit) |
| |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handleInternal(storage, admit, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| Namespace: metav1.NamespaceDefault, |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| dump, _ := httputil.DumpResponse(response, true) |
| t.Log(string(dump)) |
| |
| if response.StatusCode != http.StatusForbidden { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| } |
| |
| func TestUpdateRequiresMatchingName(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| dump, _ := httputil.DumpResponse(response, true) |
| t.Log(string(dump)) |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| func TestUpdateAllowsMissingNamespace(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| dump, _ := httputil.DumpResponse(response, true) |
| t.Log(string(dump)) |
| |
| if response.StatusCode != http.StatusOK { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| // when the object name and namespace can't be retrieved, don't update. It isn't safe. |
| func TestUpdateDisallowsMismatchedNamespaceOnError(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| err: fmt.Errorf("test error"), |
| } |
| handler := handleLinker(storage, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| Namespace: "other", // does not match request |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| dump, _ := httputil.DumpResponse(response, true) |
| t.Log(string(dump)) |
| |
| if simpleStorage.updated != nil { |
| t.Errorf("Unexpected update value %#v.", simpleStorage.updated) |
| } |
| if selfLinker.called { |
| t.Errorf("self link ignored") |
| } |
| } |
| |
| func TestUpdatePreventsMismatchedNamespace(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| simpleStorage := SimpleRESTStorage{} |
| ID := "id" |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| Namespace: "other", |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| // The following cases will fail, so die now |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| func TestUpdateMissing(t *testing.T) { |
| storage := map[string]rest.Storage{} |
| ID := "id" |
| simpleStorage := SimpleRESTStorage{ |
| errors: map[string]error{"update": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, ID)}, |
| } |
| storage["simple"] = &simpleStorage |
| handler := handle(storage) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| item := &genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ID, |
| Namespace: metav1.NamespaceDefault, |
| }, |
| Other: "bar", |
| } |
| body, err := runtime.Encode(testCodec, item) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| client := http.Client{} |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusNotFound { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| func TestCreateNotFound(t *testing.T) { |
| handler := handle(map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{ |
| // storage.Create can fail with not found error in theory. |
| // See http://pr.k8s.io/486#discussion_r15037092. |
| errors: map[string]error{"create": apierrs.NewNotFound(schema.GroupResource{Resource: "simples"}, "id")}, |
| }, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{Other: "foo"} |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if response.StatusCode != http.StatusNotFound { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| } |
| |
| func TestCreateChecksDecode(t *testing.T) { |
| handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &example.Pod{} |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| b, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } else if !strings.Contains(string(b), "cannot be handled as a Simple") { |
| t.Errorf("unexpected response: %s", string(b)) |
| } |
| } |
| |
| func TestParentResourceIsRequired(t *testing.T) { |
| storage := &SimpleTypedStorage{ |
| baseType: &genericapitesting.SimpleRoot{}, // a root scoped type |
| item: &genericapitesting.SimpleRoot{}, |
| } |
| group := &APIGroupVersion{ |
| Storage: map[string]rest.Storage{ |
| "simple/sub": storage, |
| }, |
| Root: "/" + prefix, |
| Creater: scheme, |
| Convertor: scheme, |
| UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), |
| Defaulter: scheme, |
| Typer: scheme, |
| Linker: selfLinker, |
| RootScopedKinds: sets.NewString("SimpleRoot"), |
| |
| Admit: admissionControl, |
| |
| GroupVersion: newGroupVersion, |
| OptionsExternalVersion: &newGroupVersion, |
| |
| Serializer: codecs, |
| ParameterCodec: parameterCodec, |
| } |
| container := restful.NewContainer() |
| if err := group.InstallREST(container); err == nil { |
| t.Fatal("expected error") |
| } |
| |
| storage = &SimpleTypedStorage{ |
| baseType: &genericapitesting.SimpleRoot{}, // a root scoped type |
| item: &genericapitesting.SimpleRoot{}, |
| } |
| group = &APIGroupVersion{ |
| Storage: map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/sub": storage, |
| }, |
| Root: "/" + prefix, |
| Creater: scheme, |
| Convertor: scheme, |
| UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), |
| Defaulter: scheme, |
| Typer: scheme, |
| Linker: selfLinker, |
| |
| Admit: admissionControl, |
| |
| GroupVersion: newGroupVersion, |
| OptionsExternalVersion: &newGroupVersion, |
| |
| Serializer: codecs, |
| ParameterCodec: parameterCodec, |
| } |
| container = restful.NewContainer() |
| if err := group.InstallREST(container); err != nil { |
| t.Fatal(err) |
| } |
| |
| handler := genericapifilters.WithRequestInfo(container, newTestRequestInfoResolver()) |
| |
| // resource is NOT registered in the root scope |
| w := httptest.NewRecorder() |
| handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}}) |
| if w.Code != http.StatusNotFound { |
| t.Errorf("expected not found: %#v", w) |
| } |
| |
| // resource is registered in the namespace scope |
| w = httptest.NewRecorder() |
| handler.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}}) |
| if w.Code != http.StatusOK { |
| t.Fatalf("expected OK: %#v", w) |
| } |
| if storage.actualNamespace != "test" { |
| t.Errorf("namespace should be set %#v", storage) |
| } |
| } |
| |
| func TestCreateWithName(t *testing.T) { |
| pathName := "helloworld" |
| storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}} |
| handler := handle(map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/sub": storage, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{Other: "foo"} |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+pathName+"/sub", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusCreated { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| if storage.createdName != pathName { |
| t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName) |
| } |
| } |
| |
| func TestUpdateChecksDecode(t *testing.T) { |
| handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &example.Pod{} |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| t.Errorf("Unexpected response %#v\n%s", response, readBodyOrDie(response.Body)) |
| } |
| b, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } else if !strings.Contains(string(b), "cannot be handled as a Simple") { |
| t.Errorf("unexpected response: %s", string(b)) |
| } |
| } |
| |
| type setTestSelfLinker struct { |
| t *testing.T |
| expectedSet string |
| alternativeSet sets.String |
| name string |
| namespace string |
| called bool |
| err error |
| } |
| |
| func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err } |
| func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err } |
| func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err } |
| func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error { |
| if e, a := s.expectedSet, selfLink; e != a { |
| if !s.alternativeSet.Has(a) { |
| s.t.Errorf("expected '%v', got '%v'", e, a) |
| } |
| } |
| s.called = true |
| return s.err |
| } |
| |
| func TestCreate(t *testing.T) { |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| time.Sleep(5 * time.Millisecond) |
| return obj, nil |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| name: "bar", |
| namespace: "default", |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", |
| } |
| handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{ |
| Other: "bar", |
| } |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| wg := sync.WaitGroup{} |
| wg.Add(1) |
| var response *http.Response |
| go func() { |
| response, err = client.Do(request) |
| wg.Done() |
| }() |
| wg.Wait() |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(response, &itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v %#v", err, response) |
| } |
| |
| itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !reflect.DeepEqual(&itemOut, simple) { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) |
| } |
| if response.StatusCode != http.StatusCreated { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestCreateYAML(t *testing.T) { |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| time.Sleep(5 * time.Millisecond) |
| return obj, nil |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| name: "bar", |
| namespace: "default", |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", |
| } |
| handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| // yaml encoder |
| simple := &genericapitesting.Simple{ |
| Other: "bar", |
| } |
| info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/yaml") |
| if !ok { |
| t.Fatal("No yaml serializer") |
| } |
| encoder := codecs.EncoderForVersion(info.Serializer, testGroupVersion) |
| decoder := codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion) |
| |
| data, err := runtime.Encode(encoder, simple) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| request.Header.Set("Accept", "application/yaml, application/json") |
| request.Header.Set("Content-Type", "application/yaml") |
| |
| wg := sync.WaitGroup{} |
| wg.Add(1) |
| var response *http.Response |
| go func() { |
| response, err = client.Do(request) |
| wg.Done() |
| }() |
| wg.Wait() |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| var itemOut genericapitesting.Simple |
| body, err := extractBodyDecoder(response, &itemOut, decoder) |
| if err != nil { |
| t.Fatalf("unexpected error: %v %#v", err, response) |
| } |
| |
| itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !reflect.DeepEqual(&itemOut, simple) { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) |
| } |
| if response.StatusCode != http.StatusCreated { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| func TestCreateInNamespace(t *testing.T) { |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| time.Sleep(5 * time.Millisecond) |
| return obj, nil |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| name: "bar", |
| namespace: "other", |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", |
| } |
| handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{ |
| Other: "bar", |
| } |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| wg := sync.WaitGroup{} |
| wg.Add(1) |
| var response *http.Response |
| go func() { |
| response, err = client.Do(request) |
| wg.Done() |
| }() |
| wg.Wait() |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| |
| var itemOut genericapitesting.Simple |
| body, err := extractBody(response, &itemOut) |
| if err != nil { |
| t.Fatalf("unexpected error: %v\n%s", err, data) |
| } |
| |
| itemOut.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) |
| if !reflect.DeepEqual(&itemOut, simple) { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) |
| } |
| if response.StatusCode != http.StatusCreated { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) |
| } |
| if !selfLinker.called { |
| t.Errorf("Never set self link") |
| } |
| } |
| |
| func TestCreateInvokeAdmissionControl(t *testing.T) { |
| for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} { |
| t.Logf("Testing %T", admit) |
| |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| time.Sleep(5 * time.Millisecond) |
| return obj, nil |
| }, |
| } |
| selfLinker := &setTestSelfLinker{ |
| t: t, |
| name: "bar", |
| namespace: "other", |
| expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", |
| } |
| handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, selfLinker, nil) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{ |
| Other: "bar", |
| } |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| wg := sync.WaitGroup{} |
| wg.Add(1) |
| var response *http.Response |
| go func() { |
| response, err = client.Do(request) |
| wg.Done() |
| }() |
| wg.Wait() |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusForbidden { |
| t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response) |
| } |
| } |
| } |
| |
| func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *metav1.Status { |
| t.Helper() |
| client := http.Client{} |
| request, err := http.NewRequest(method, url, bytes.NewBuffer(data)) |
| if err != nil { |
| t.Fatalf("unexpected error %#v", err) |
| return nil |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error on %s %s: %v", method, url, err) |
| return nil |
| } |
| var status metav1.Status |
| body, err := extractBody(response, &status) |
| if err != nil { |
| t.Fatalf("unexpected error on %s %s: %v\nbody:\n%s", method, url, err, body) |
| return nil |
| } |
| if code != response.StatusCode { |
| t.Fatalf("Expected %s %s to return %d, Got %d: %v", method, url, code, response.StatusCode, body) |
| } |
| return &status |
| } |
| |
| func TestDelayReturnsError(t *testing.T) { |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| return nil, apierrs.NewAlreadyExists(schema.GroupResource{Resource: "foos"}, "bar") |
| }, |
| } |
| handler := handle(map[string]rest.Storage{"foo": &storage}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo/bar", server.URL), nil, http.StatusConflict) |
| if status.Status != metav1.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != metav1.StatusReasonAlreadyExists { |
| t.Errorf("Unexpected status %#v", status) |
| } |
| } |
| |
| type UnregisteredAPIObject struct { |
| Value string |
| } |
| |
| func (obj *UnregisteredAPIObject) GetObjectKind() schema.ObjectKind { |
| return schema.EmptyObjectKind |
| } |
| func (obj *UnregisteredAPIObject) DeepCopyObject() runtime.Object { |
| if obj == nil { |
| return nil |
| } |
| clone := *obj |
| return &clone |
| } |
| |
| func TestWriteJSONDecodeError(t *testing.T) { |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| responsewriters.WriteObjectNegotiated(codecs, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"}) |
| })) |
| defer server.Close() |
| // Decode error response behavior is dictated by |
| // apiserver/pkg/endpoints/handlers/responsewriters/status.go::ErrorToAPIStatus(). |
| // Unless specific metav1.Status() parameters are implemented for the particular error in question, such that |
| // the status code is defined, metav1 errors where error.status == metav1.StatusFailure |
| // will throw a '500 Internal Server Error'. Non-metav1 type errors will always throw a '500 Internal Server Error'. |
| status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError) |
| if status.Reason != metav1.StatusReasonUnknown { |
| t.Errorf("unexpected reason %#v", status) |
| } |
| if !strings.Contains(status.Message, "no kind is registered for the type endpoints.UnregisteredAPIObject") { |
| t.Errorf("unexpected message %#v", status) |
| } |
| } |
| |
| type marshalError struct { |
| err error |
| } |
| |
| func (m *marshalError) MarshalJSON() ([]byte, error) { |
| return []byte{}, m.err |
| } |
| |
| func TestWriteRAWJSONMarshalError(t *testing.T) { |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| responsewriters.WriteRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w) |
| })) |
| defer server.Close() |
| client := http.Client{} |
| resp, err := client.Get(server.URL) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if resp.StatusCode != http.StatusInternalServerError { |
| t.Errorf("unexpected status code %d", resp.StatusCode) |
| } |
| } |
| |
| func TestCreateTimeout(t *testing.T) { |
| testOver := make(chan struct{}) |
| defer close(testOver) |
| storage := SimpleRESTStorage{ |
| injectedFunction: func(obj runtime.Object) (runtime.Object, error) { |
| // Eliminate flakes by ensuring the create operation takes longer than this test. |
| <-testOver |
| return obj, nil |
| }, |
| } |
| handler := handle(map[string]rest.Storage{ |
| "foo": &storage, |
| }) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| |
| simple := &genericapitesting.Simple{Other: "foo"} |
| data, err := runtime.Encode(testCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| itemOut := expectApiStatus(t, "POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo?timeout=4ms", data, http.StatusGatewayTimeout) |
| if itemOut.Status != metav1.StatusFailure || itemOut.Reason != metav1.StatusReasonTimeout { |
| t.Errorf("Unexpected status %#v", itemOut) |
| } |
| } |
| |
| func TestCreateChecksAPIVersion(t *testing.T) { |
| handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{} |
| //using newCodec and send the request to testVersion URL shall cause a discrepancy in apiVersion |
| data, err := runtime.Encode(newCodec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| b, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } else if !strings.Contains(string(b), "does not match the expected API version") { |
| t.Errorf("unexpected response: %s", string(b)) |
| } |
| } |
| |
| func TestCreateDefaultsAPIVersion(t *testing.T) { |
| handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{} |
| data, err := runtime.Encode(codec, simple) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| m := make(map[string]interface{}) |
| if err := json.Unmarshal(data, &m); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| delete(m, "apiVersion") |
| data, err = json.Marshal(m) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusCreated { |
| t.Errorf("unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusCreated, response) |
| } |
| } |
| |
| func TestUpdateChecksAPIVersion(t *testing.T) { |
| handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| simple := &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} |
| data, err := runtime.Encode(newCodec, simple) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| response, err := client.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| t.Errorf("Unexpected response %#v", response) |
| } |
| b, err := ioutil.ReadAll(response.Body) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } else if !strings.Contains(string(b), "does not match the expected API version") { |
| t.Errorf("unexpected response: %s", string(b)) |
| } |
| } |
| |
| // runRequest is used by TestDryRun since it runs the test twice in a |
| // row with a slightly different URL (one has ?dryRun, one doesn't). |
| func runRequest(t *testing.T, path, verb string, data []byte, contentType string) *http.Response { |
| request, err := http.NewRequest(verb, path, bytes.NewBuffer(data)) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if contentType != "" { |
| request.Header.Set("Content-Type", contentType) |
| } |
| response, err := http.DefaultClient.Do(request) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| return response |
| } |
| |
| // encodeOrFatal is used by TestDryRun to parse an object and stop right |
| // away if it fails. |
| func encodeOrFatal(t *testing.T, obj runtime.Object) []byte { |
| data, err := runtime.Encode(testCodec, obj) |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| return data |
| } |
| |
| type SimpleRESTStorageWithDeleteCollection struct { |
| SimpleRESTStorage |
| } |
| |
| // Delete collection doesn't do much, but let us test this path. |
| func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { |
| storage.checkContext(ctx) |
| return nil, nil |
| } |
| |
| func TestDryRunDisabled(t *testing.T) { |
| defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DryRun, false)() |
| |
| tests := []struct { |
| path string |
| verb string |
| data []byte |
| contentType string |
| }{ |
| {path: "/namespaces/default/simples", verb: "POST", data: encodeOrFatal(t, &genericapitesting.Simple{Other: "bar"})}, |
| {path: "/namespaces/default/simples/id", verb: "PUT", data: encodeOrFatal(t, &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "id"}, Other: "bar"})}, |
| {path: "/namespaces/default/simples/id", verb: "PATCH", data: []byte(`{"labels":{"foo":"bar"}}`), contentType: "application/merge-patch+json; charset=UTF-8"}, |
| {path: "/namespaces/default/simples/id", verb: "DELETE"}, |
| {path: "/namespaces/default/simples", verb: "DELETE"}, |
| {path: "/namespaces/default/simples/id/subsimple", verb: "DELETE"}, |
| } |
| |
| server := httptest.NewServer(handle(map[string]rest.Storage{ |
| "simples": &SimpleRESTStorageWithDeleteCollection{ |
| SimpleRESTStorage{ |
| item: genericapitesting.Simple{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "id", |
| Namespace: "", |
| UID: "uid", |
| }, |
| Other: "bar", |
| }, |
| }, |
| }, |
| "simples/subsimple": &SimpleXGSubresourceRESTStorage{ |
| item: genericapitesting.SimpleXGSubresource{ |
| SubresourceInfo: "foo", |
| }, |
| itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), |
| }, |
| })) |
| defer server.Close() |
| for _, test := range tests { |
| baseUrl := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version |
| response := runRequest(t, baseUrl+test.path, test.verb, test.data, test.contentType) |
| if response.StatusCode == http.StatusBadRequest { |
| t.Fatalf("unexpected BadRequest: %#v", response) |
| } |
| response = runRequest(t, baseUrl+test.path+"?dryRun", test.verb, test.data, test.contentType) |
| if response.StatusCode != http.StatusBadRequest { |
| t.Fatalf("unexpected non BadRequest: %#v", response) |
| } |
| } |
| } |
| |
| type SimpleXGSubresourceRESTStorage struct { |
| item genericapitesting.SimpleXGSubresource |
| itemGVK schema.GroupVersionKind |
| } |
| |
| var _ = rest.GroupVersionKindProvider(&SimpleXGSubresourceRESTStorage{}) |
| |
| func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object { |
| return &genericapitesting.SimpleXGSubresource{} |
| } |
| |
| func (storage *SimpleXGSubresourceRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) { |
| return storage.item.DeepCopyObject(), nil |
| } |
| |
| func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) { |
| return nil, true, nil |
| } |
| |
| func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { |
| return storage.itemGVK |
| } |
| |
| func TestXGSubresource(t *testing.T) { |
| container := restful.NewContainer() |
| container.Router(restful.CurlyRouter{}) |
| mux := container.ServeMux |
| |
| itemID := "theID" |
| subresourceStorage := &SimpleXGSubresourceRESTStorage{ |
| item: genericapitesting.SimpleXGSubresource{ |
| SubresourceInfo: "foo", |
| }, |
| itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"), |
| } |
| storage := map[string]rest.Storage{ |
| "simple": &SimpleRESTStorage{}, |
| "simple/subsimple": subresourceStorage, |
| } |
| |
| group := APIGroupVersion{ |
| Storage: storage, |
| |
| Creater: scheme, |
| Convertor: scheme, |
| UnsafeConvertor: runtime.UnsafeObjectConvertor(scheme), |
| Defaulter: scheme, |
| Typer: scheme, |
| Linker: selfLinker, |
| |
| ParameterCodec: parameterCodec, |
| |
| Admit: admissionControl, |
| |
| Root: "/" + prefix, |
| GroupVersion: testGroupVersion, |
| OptionsExternalVersion: &testGroupVersion, |
| Serializer: codecs, |
| } |
| |
| if err := (&group).InstallREST(container); err != nil { |
| panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) |
| } |
| |
| server := newTestServer(defaultAPIServer{mux, container}) |
| defer server.Close() |
| |
| resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple") |
| if err != nil { |
| t.Fatalf("unexpected error: %v", err) |
| } |
| if resp.StatusCode != http.StatusOK { |
| t.Fatalf("unexpected response: %#v", resp) |
| } |
| var itemOut genericapitesting.SimpleXGSubresource |
| body, err := extractBody(resp, &itemOut) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| // Test if the returned object has the expected group, version and kind |
| // We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the |
| // installed decoders. TypeMeta cannot be decoded because it is added to the ignored |
| // conversion type list in API scheme and hence cannot be converted from input type object |
| // to output type object. So it's values don't appear in the decoded output object. |
| decoder := json.NewDecoder(strings.NewReader(body)) |
| var itemFromBody genericapitesting.SimpleXGSubresource |
| err = decoder.Decode(&itemFromBody) |
| if err != nil { |
| t.Errorf("unexpected JSON decoding error: %v", err) |
| } |
| if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want { |
| t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want) |
| } |
| if itemFromBody.Kind != "SimpleXGSubresource" { |
| t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind) |
| } |
| |
| if itemOut.Name != subresourceStorage.item.Name { |
| t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body)) |
| } |
| } |
| |
| func readBodyOrDie(r io.Reader) []byte { |
| body, err := ioutil.ReadAll(r) |
| if err != nil { |
| panic(err) |
| } |
| return body |
| } |
| |
| // BenchmarkUpdateProtobuf measures the cost of processing an update on the server in proto |
| func BenchmarkUpdateProtobuf(b *testing.B) { |
| items := benchmarkItems(b) |
| |
| simpleStorage := &SimpleRESTStorage{} |
| handler := handle(map[string]rest.Storage{"simples": simpleStorage}) |
| server := httptest.NewServer(handler) |
| defer server.Close() |
| client := http.Client{} |
| |
| dest, _ := url.Parse(server.URL) |
| dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simples/bar" |
| dest.RawQuery = "" |
| |
| info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf") |
| e := codecs.EncoderForVersion(info.Serializer, newGroupVersion) |
| data, err := runtime.Encode(e, &items[0]) |
| if err != nil { |
| b.Fatal(err) |
| } |
| |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| request, err := http.NewRequest("PUT", dest.String(), bytes.NewReader(data)) |
| if err != nil { |
| b.Fatalf("unexpected error: %v", err) |
| } |
| request.Header.Set("Accept", "application/vnd.kubernetes.protobuf") |
| request.Header.Set("Content-Type", "application/vnd.kubernetes.protobuf") |
| response, err := client.Do(request) |
| if err != nil { |
| b.Fatalf("unexpected error: %v", err) |
| } |
| if response.StatusCode != http.StatusBadRequest { |
| body, _ := ioutil.ReadAll(response.Body) |
| b.Fatalf("Unexpected response %#v\n%s", response, body) |
| } |
| _, _ = ioutil.ReadAll(response.Body) |
| response.Body.Close() |
| } |
| b.StopTimer() |
| } |
| |
| func newTestServer(handler http.Handler) *httptest.Server { |
| handler = genericapifilters.WithRequestInfo(handler, newTestRequestInfoResolver()) |
| return httptest.NewServer(handler) |
| } |
| |
| func newTestRequestInfoResolver() *request.RequestInfoFactory { |
| return &request.RequestInfoFactory{ |
| APIPrefixes: sets.NewString("api", "apis"), |
| GrouplessAPIPrefixes: sets.NewString("api"), |
| } |
| } |
| |
| const benchmarkSeed = 100 |
| |
| func benchmarkItems(b *testing.B) []example.Pod { |
| clientapiObjectFuzzer := fuzzer.FuzzerFor(examplefuzzer.Funcs, rand.NewSource(benchmarkSeed), codecs) |
| items := make([]example.Pod, 3) |
| for i := range items { |
| clientapiObjectFuzzer.Fuzz(&items[i]) |
| } |
| return items |
| } |