diff --git a/pkg/registry/generic/BUILD b/pkg/registry/generic/BUILD index b46fb9eeb..43471c043 100644 --- a/pkg/registry/generic/BUILD +++ b/pkg/registry/generic/BUILD @@ -39,6 +39,7 @@ filegroup( ":package-srcs", "//staging/src/k8s.io/apiserver/pkg/registry/generic/registry:all-srcs", "//staging/src/k8s.io/apiserver/pkg/registry/generic/rest:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/registry/generic/testing/BUILD b/pkg/registry/generic/testing/BUILD new file mode 100644 index 000000000..088c78d67 --- /dev/null +++ b/pkg/registry/generic/testing/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["tester.go"], + importpath = "k8s.io/apiserver/pkg/registry/generic/testing", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest/resttest:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/etcd:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/registry/generic/testing/tester.go b/pkg/registry/generic/testing/tester.go new file mode 100644 index 000000000..4125e40b3 --- /dev/null +++ b/pkg/registry/generic/testing/tester.go @@ -0,0 +1,202 @@ +/* +Copyright 2015 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 tester + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest/resttest" + etcdstorage "k8s.io/apiserver/pkg/storage/etcd" + storagetesting "k8s.io/apiserver/pkg/storage/testing" +) + +type Tester struct { + tester *resttest.Tester + storage *genericregistry.Store +} +type UpdateFunc func(runtime.Object) runtime.Object + +func New(t *testing.T, storage *genericregistry.Store, scheme *runtime.Scheme) *Tester { + return &Tester{ + tester: resttest.New(t, storage, scheme), + storage: storage, + } +} + +func (t *Tester) TestNamespace() string { + return t.tester.TestNamespace() +} + +func (t *Tester) ClusterScope() *Tester { + t.tester = t.tester.ClusterScope() + return t +} + +func (t *Tester) Namer(namer func(int) string) *Tester { + t.tester = t.tester.Namer(namer) + return t +} + +func (t *Tester) AllowCreateOnUpdate() *Tester { + t.tester = t.tester.AllowCreateOnUpdate() + return t +} + +func (t *Tester) GeneratesName() *Tester { + t.tester = t.tester.GeneratesName() + return t +} + +func (t *Tester) ReturnDeletedObject() *Tester { + t.tester = t.tester.ReturnDeletedObject() + return t +} + +func (t *Tester) TestCreate(valid runtime.Object, invalid ...runtime.Object) { + t.tester.TestCreate( + valid, + t.createObject, + t.getObject, + invalid..., + ) +} + +func (t *Tester) TestUpdate(valid runtime.Object, validUpdateFunc UpdateFunc, invalidUpdateFunc ...UpdateFunc) { + var invalidFuncs []resttest.UpdateFunc + for _, f := range invalidUpdateFunc { + invalidFuncs = append(invalidFuncs, resttest.UpdateFunc(f)) + } + t.tester.TestUpdate( + valid, + t.createObject, + t.getObject, + resttest.UpdateFunc(validUpdateFunc), + invalidFuncs..., + ) +} + +func (t *Tester) TestDelete(valid runtime.Object) { + t.tester.TestDelete( + valid, + t.createObject, + t.getObject, + errors.IsNotFound, + ) +} + +func (t *Tester) TestDeleteGraceful(valid runtime.Object, expectedGrace int64) { + t.tester.TestDeleteGraceful( + valid, + t.createObject, + t.getObject, + expectedGrace, + ) +} + +func (t *Tester) TestGet(valid runtime.Object) { + t.tester.TestGet(valid) +} + +func (t *Tester) TestList(valid runtime.Object) { + t.tester.TestList( + valid, + t.setObjectsForList, + ) +} + +func (t *Tester) TestWatch(valid runtime.Object, labelsPass, labelsFail []labels.Set, fieldsPass, fieldsFail []fields.Set) { + t.tester.TestWatch( + valid, + t.emitObject, + labelsPass, + labelsFail, + fieldsPass, + fieldsFail, + // TODO: This should be filtered, the registry should not be aware of this level of detail + []string{etcdstorage.EtcdCreate, etcdstorage.EtcdDelete}, + ) +} + +// Helper functions + +func (t *Tester) getObject(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + + result, err := t.storage.Get(ctx, accessor.GetName(), &metav1.GetOptions{}) + if err != nil { + return nil, err + } + return result, nil +} + +func (t *Tester) createObject(ctx genericapirequest.Context, obj runtime.Object) error { + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } + key, err := t.storage.KeyFunc(ctx, accessor.GetName()) + if err != nil { + return err + } + return t.storage.Storage.Create(ctx, key, obj, nil, 0) +} + +func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object { + key := t.storage.KeyRootFunc(t.tester.TestContext()) + if _, err := t.storage.DeleteCollection(t.tester.TestContext(), nil, nil); err != nil { + t.tester.Errorf("unable to clear collection: %v", err) + return nil + } + if err := storagetesting.CreateObjList(key, t.storage.Storage, objects); err != nil { + t.tester.Errorf("unexpected error: %v", err) + return nil + } + return objects +} + +func (t *Tester) emitObject(obj runtime.Object, action string) error { + ctx := t.tester.TestContext() + var err error + + switch action { + case etcdstorage.EtcdCreate: + err = t.createObject(ctx, obj) + case etcdstorage.EtcdDelete: + var accessor metav1.Object + accessor, err = meta.Accessor(obj) + if err != nil { + return err + } + _, _, err = t.storage.Delete(ctx, accessor.GetName(), nil) + default: + err = fmt.Errorf("unexpected action: %v", action) + } + + return err +}