| /* |
| 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 resttest |
| |
| import ( |
| "context" |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| "time" |
| |
| apiequality "k8s.io/apimachinery/pkg/api/equality" |
| "k8s.io/apimachinery/pkg/api/errors" |
| "k8s.io/apimachinery/pkg/api/meta" |
| "k8s.io/apimachinery/pkg/api/validation/path" |
| metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/conversion" |
| "k8s.io/apimachinery/pkg/fields" |
| "k8s.io/apimachinery/pkg/labels" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/wait" |
| genericapirequest "k8s.io/apiserver/pkg/endpoints/request" |
| "k8s.io/apiserver/pkg/registry/rest" |
| ) |
| |
| // TODO(apelisse): Tests in this file should be more hermertic by always |
| // removing objects that they create. That would avoid name-collisions. |
| |
| type Tester struct { |
| *testing.T |
| storage rest.Storage |
| clusterScope bool |
| createOnUpdate bool |
| generatesName bool |
| returnDeletedObject bool |
| namer func(int) string |
| } |
| |
| func New(t *testing.T, storage rest.Storage) *Tester { |
| return &Tester{ |
| T: t, |
| storage: storage, |
| namer: defaultNamer, |
| } |
| } |
| |
| func defaultNamer(i int) string { |
| return fmt.Sprintf("foo%d", i) |
| } |
| |
| // Namer allows providing a custom name maker |
| // By default "foo%d" is used |
| func (t *Tester) Namer(namer func(int) string) *Tester { |
| t.namer = namer |
| return t |
| } |
| |
| func (t *Tester) ClusterScope() *Tester { |
| t.clusterScope = true |
| return t |
| } |
| |
| func (t *Tester) AllowCreateOnUpdate() *Tester { |
| t.createOnUpdate = true |
| return t |
| } |
| |
| func (t *Tester) GeneratesName() *Tester { |
| t.generatesName = true |
| return t |
| } |
| |
| func (t *Tester) ReturnDeletedObject() *Tester { |
| t.returnDeletedObject = true |
| return t |
| } |
| |
| // TestNamespace returns the namespace that will be used when creating contexts. |
| // Returns NamespaceNone for cluster-scoped objects. |
| func (t *Tester) TestNamespace() string { |
| if t.clusterScope { |
| return metav1.NamespaceNone |
| } |
| return "test" |
| } |
| |
| // TestContext returns a namespaced context that will be used when making storage calls. |
| // Namespace is determined by TestNamespace() |
| func (t *Tester) TestContext() context.Context { |
| if t.clusterScope { |
| return genericapirequest.NewContext() |
| } |
| return genericapirequest.WithNamespace(genericapirequest.NewContext(), t.TestNamespace()) |
| } |
| |
| func (t *Tester) getObjectMetaOrFail(obj runtime.Object) metav1.Object { |
| objMeta, err := meta.Accessor(obj) |
| if err != nil { |
| t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, obj) |
| } |
| return objMeta |
| } |
| |
| func (t *Tester) setObjectMeta(obj runtime.Object, name string) { |
| meta := t.getObjectMetaOrFail(obj) |
| meta.SetName(name) |
| if t.clusterScope { |
| meta.SetNamespace(metav1.NamespaceNone) |
| } else { |
| meta.SetNamespace(genericapirequest.NamespaceValue(t.TestContext())) |
| } |
| meta.SetGenerateName("") |
| meta.SetGeneration(1) |
| } |
| |
| type AssignFunc func([]runtime.Object) []runtime.Object |
| type EmitFunc func(runtime.Object, string) error |
| type GetFunc func(context.Context, runtime.Object) (runtime.Object, error) |
| type InitWatchFunc func() |
| type InjectErrFunc func(err error) |
| type IsErrorFunc func(err error) bool |
| type CreateFunc func(context.Context, runtime.Object) error |
| type SetRVFunc func(uint64) |
| type UpdateFunc func(runtime.Object) runtime.Object |
| |
| // Test creating an object. |
| func (t *Tester) TestCreate(valid runtime.Object, createFn CreateFunc, getFn GetFunc, invalid ...runtime.Object) { |
| dryRunOpts := metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}} |
| opts := metav1.CreateOptions{} |
| t.testCreateHasMetadata(valid.DeepCopyObject()) |
| if !t.generatesName { |
| t.testCreateGeneratesName(valid.DeepCopyObject()) |
| } |
| t.testCreateDryRun(valid.DeepCopyObject(), getFn) |
| t.testCreateDryRunEquals(valid.DeepCopyObject()) |
| t.testCreateEquals(valid.DeepCopyObject(), getFn) |
| t.testCreateAlreadyExisting(valid.DeepCopyObject(), createFn, dryRunOpts) |
| t.testCreateAlreadyExisting(valid.DeepCopyObject(), createFn, opts) |
| if t.clusterScope { |
| t.testCreateDiscardsObjectNamespace(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateDiscardsObjectNamespace(valid.DeepCopyObject(), opts) |
| t.testCreateIgnoresContextNamespace(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateIgnoresContextNamespace(valid.DeepCopyObject(), opts) |
| t.testCreateIgnoresMismatchedNamespace(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateIgnoresMismatchedNamespace(valid.DeepCopyObject(), opts) |
| t.testCreateResetsUserData(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateResetsUserData(valid.DeepCopyObject(), opts) |
| } else { |
| t.testCreateRejectsMismatchedNamespace(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateRejectsMismatchedNamespace(valid.DeepCopyObject(), opts) |
| } |
| t.testCreateInvokesValidation(dryRunOpts, invalid...) |
| t.testCreateInvokesValidation(opts, invalid...) |
| t.testCreateValidatesNames(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateValidatesNames(valid.DeepCopyObject(), opts) |
| t.testCreateIgnoreClusterName(valid.DeepCopyObject(), dryRunOpts) |
| t.testCreateIgnoreClusterName(valid.DeepCopyObject(), opts) |
| } |
| |
| // Test updating an object. |
| func (t *Tester) TestUpdate(valid runtime.Object, createFn CreateFunc, getFn GetFunc, updateFn UpdateFunc, invalidUpdateFn ...UpdateFunc) { |
| dryRunOpts := metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}} |
| opts := metav1.UpdateOptions{} |
| t.testUpdateEquals(valid.DeepCopyObject(), createFn, getFn, updateFn) |
| t.testUpdateFailsOnVersionTooOld(valid.DeepCopyObject(), createFn, getFn) |
| t.testUpdateOnNotFound(valid.DeepCopyObject(), dryRunOpts) |
| t.testUpdateOnNotFound(valid.DeepCopyObject(), opts) |
| if !t.clusterScope { |
| t.testUpdateRejectsMismatchedNamespace(valid.DeepCopyObject(), createFn, getFn) |
| } |
| t.testUpdateInvokesValidation(valid.DeepCopyObject(), createFn, invalidUpdateFn...) |
| t.testUpdateWithWrongUID(valid.DeepCopyObject(), createFn, getFn, dryRunOpts) |
| t.testUpdateWithWrongUID(valid.DeepCopyObject(), createFn, getFn, opts) |
| t.testUpdateRetrievesOldObject(valid.DeepCopyObject(), createFn, getFn) |
| t.testUpdatePropagatesUpdatedObjectError(valid.DeepCopyObject(), createFn, getFn, dryRunOpts) |
| t.testUpdatePropagatesUpdatedObjectError(valid.DeepCopyObject(), createFn, getFn, opts) |
| t.testUpdateIgnoreGenerationUpdates(valid.DeepCopyObject(), createFn, getFn) |
| t.testUpdateIgnoreClusterName(valid.DeepCopyObject(), createFn, getFn) |
| } |
| |
| // Test deleting an object. |
| func (t *Tester) TestDelete(valid runtime.Object, createFn CreateFunc, getFn GetFunc, isNotFoundFn IsErrorFunc) { |
| dryRunOpts := metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}} |
| opts := metav1.DeleteOptions{} |
| t.testDeleteNonExist(valid.DeepCopyObject(), dryRunOpts) |
| t.testDeleteNonExist(valid.DeepCopyObject(), opts) |
| t.testDeleteNoGraceful(valid.DeepCopyObject(), createFn, getFn, isNotFoundFn, true) |
| t.testDeleteNoGraceful(valid.DeepCopyObject(), createFn, getFn, isNotFoundFn, false) |
| t.testDeleteWithUID(valid.DeepCopyObject(), createFn, getFn, isNotFoundFn, dryRunOpts) |
| t.testDeleteWithUID(valid.DeepCopyObject(), createFn, getFn, isNotFoundFn, opts) |
| } |
| |
| // Test gracefully deleting an object. |
| func (t *Tester) TestDeleteGraceful(valid runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| t.testDeleteDryRunGracefulHasdefault(valid.DeepCopyObject(), createFn, expectedGrace) |
| t.testDeleteGracefulHasDefault(valid.DeepCopyObject(), createFn, getFn, expectedGrace) |
| t.testDeleteGracefulWithValue(valid.DeepCopyObject(), createFn, getFn, expectedGrace) |
| t.testDeleteGracefulUsesZeroOnNil(valid.DeepCopyObject(), createFn, expectedGrace) |
| t.testDeleteGracefulExtend(valid.DeepCopyObject(), createFn, getFn, expectedGrace) |
| t.testDeleteGracefulShorten(valid.DeepCopyObject(), createFn, getFn, expectedGrace) |
| t.testDeleteGracefulImmediate(valid.DeepCopyObject(), createFn, getFn, expectedGrace) |
| } |
| |
| // Test getting object. |
| func (t *Tester) TestGet(valid runtime.Object) { |
| t.testGetFound(valid.DeepCopyObject()) |
| t.testGetNotFound(valid.DeepCopyObject()) |
| t.testGetMimatchedNamespace(valid.DeepCopyObject()) |
| if !t.clusterScope { |
| t.testGetDifferentNamespace(valid.DeepCopyObject()) |
| } |
| } |
| |
| // Test listing objects. |
| func (t *Tester) TestList(valid runtime.Object, assignFn AssignFunc) { |
| t.testListNotFound(assignFn) |
| t.testListFound(valid.DeepCopyObject(), assignFn) |
| t.testListMatchLabels(valid.DeepCopyObject(), assignFn) |
| t.testListTableConversion(valid.DeepCopyObject(), assignFn) |
| } |
| |
| // Test watching objects. |
| func (t *Tester) TestWatch( |
| valid runtime.Object, emitFn EmitFunc, |
| labelsPass, labelsFail []labels.Set, fieldsPass, fieldsFail []fields.Set, actions []string) { |
| t.testWatchLabels(valid.DeepCopyObject(), emitFn, labelsPass, labelsFail, actions) |
| t.testWatchFields(valid.DeepCopyObject(), emitFn, fieldsPass, fieldsFail, actions) |
| } |
| |
| // ============================================================================= |
| // Creation tests. |
| |
| func (t *Tester) delete(ctx context.Context, obj runtime.Object) error { |
| objectMeta, err := meta.Accessor(obj) |
| if err != nil { |
| return err |
| } |
| deleter, ok := t.storage.(rest.GracefulDeleter) |
| if !ok { |
| return fmt.Errorf("Expected deleting storage, got %v", t.storage) |
| } |
| _, _, err = deleter.Delete(ctx, objectMeta.GetName(), nil) |
| return err |
| } |
| |
| func (t *Tester) testCreateAlreadyExisting(obj runtime.Object, createFn CreateFunc, opts metav1.CreateOptions) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| defer t.delete(ctx, foo) |
| |
| _, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, &opts) |
| if !errors.IsAlreadyExists(err) { |
| t.Errorf("expected already exists err, got %v", err) |
| } |
| } |
| |
| func (t *Tester) testCreateDryRun(obj runtime.Object, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(2)) |
| |
| _, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, &metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| _, err = getFn(ctx, foo) |
| if !errors.IsNotFound(err) { |
| t.Errorf("Expected NotFound error, got '%v'", err) |
| } |
| } |
| |
| func (t *Tester) testCreateDryRunEquals(obj runtime.Object) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(2)) |
| |
| createdFake, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, &metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| created, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| defer t.delete(ctx, created) |
| |
| // Set resource version which might be unset in created object. |
| createdMeta := t.getObjectMetaOrFail(created) |
| createdFakeMeta := t.getObjectMetaOrFail(createdFake) |
| createdMeta.SetCreationTimestamp(createdFakeMeta.GetCreationTimestamp()) |
| createdFakeMeta.SetResourceVersion("") |
| createdMeta.SetResourceVersion("") |
| createdMeta.SetUID(createdFakeMeta.GetUID()) |
| |
| if e, a := created, createdFake; !apiequality.Semantic.DeepEqual(e, a) { |
| t.Errorf("unexpected obj: %#v, expected %#v", e, a) |
| } |
| } |
| |
| func (t *Tester) testCreateEquals(obj runtime.Object, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(2)) |
| |
| created, err := t.storage.(rest.Creater).Create(ctx, foo, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| defer t.delete(ctx, created) |
| |
| got, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| // Set resource version which might be unset in created object. |
| createdMeta := t.getObjectMetaOrFail(created) |
| gotMeta := t.getObjectMetaOrFail(got) |
| createdMeta.SetResourceVersion(gotMeta.GetResourceVersion()) |
| |
| if e, a := created, got; !apiequality.Semantic.DeepEqual(e, a) { |
| t.Errorf("unexpected obj: %#v, expected %#v", e, a) |
| } |
| } |
| |
| func (t *Tester) testCreateDiscardsObjectNamespace(valid runtime.Object, opts metav1.CreateOptions) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| |
| // Ignore non-empty namespace in object meta |
| objectMeta.SetNamespace("not-default") |
| |
| // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted |
| created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| defer t.delete(t.TestContext(), created) |
| createdObjectMeta := t.getObjectMetaOrFail(created) |
| if createdObjectMeta.GetNamespace() != metav1.NamespaceNone { |
| t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.GetNamespace()) |
| } |
| } |
| |
| func (t *Tester) testCreateGeneratesName(valid runtime.Object) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| objectMeta.SetName("") |
| objectMeta.SetGenerateName("test-") |
| |
| created, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| defer t.delete(t.TestContext(), created) |
| if objectMeta.GetName() == "test-" || !strings.HasPrefix(objectMeta.GetName(), "test-") { |
| t.Errorf("unexpected name: %#v", valid) |
| } |
| } |
| |
| func (t *Tester) testCreateHasMetadata(valid runtime.Object) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| objectMeta.SetName(t.namer(1)) |
| objectMeta.SetNamespace(t.TestNamespace()) |
| |
| obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if obj == nil { |
| t.Fatalf("Unexpected object from result: %#v", obj) |
| } |
| defer t.delete(t.TestContext(), obj) |
| if !metav1.HasObjectMetaSystemFieldValues(objectMeta) { |
| t.Errorf("storage did not populate object meta field values") |
| } |
| } |
| |
| func (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object, opts metav1.CreateOptions) { |
| // Ignore non-empty namespace in context |
| ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") |
| |
| // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted |
| created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| defer t.delete(ctx, created) |
| createdObjectMeta := t.getObjectMetaOrFail(created) |
| if createdObjectMeta.GetNamespace() != metav1.NamespaceNone { |
| t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.GetNamespace()) |
| } |
| } |
| |
| func (t *Tester) testCreateIgnoresMismatchedNamespace(valid runtime.Object, opts metav1.CreateOptions) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| |
| // Ignore non-empty namespace in object meta |
| objectMeta.SetNamespace("not-default") |
| ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "not-default2") |
| |
| // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted |
| created, err := t.storage.(rest.Creater).Create(ctx, valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| defer t.delete(ctx, created) |
| createdObjectMeta := t.getObjectMetaOrFail(created) |
| if createdObjectMeta.GetNamespace() != metav1.NamespaceNone { |
| t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.GetNamespace()) |
| } |
| } |
| |
| func (t *Tester) testCreateValidatesNames(valid runtime.Object, opts metav1.CreateOptions) { |
| for _, invalidName := range path.NameMayNotBe { |
| objCopy := valid.DeepCopyObject() |
| objCopyMeta := t.getObjectMetaOrFail(objCopy) |
| objCopyMeta.SetName(invalidName) |
| |
| ctx := t.TestContext() |
| _, err := t.storage.(rest.Creater).Create(ctx, objCopy, rest.ValidateAllObjectFunc, &opts) |
| if !errors.IsInvalid(err) { |
| t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidName, err) |
| } |
| } |
| |
| for _, invalidSuffix := range path.NameMayNotContain { |
| objCopy := valid.DeepCopyObject() |
| objCopyMeta := t.getObjectMetaOrFail(objCopy) |
| objCopyMeta.SetName(objCopyMeta.GetName() + invalidSuffix) |
| |
| ctx := t.TestContext() |
| _, err := t.storage.(rest.Creater).Create(ctx, objCopy, rest.ValidateAllObjectFunc, &opts) |
| if !errors.IsInvalid(err) { |
| t.Errorf("%s: Expected to get an invalid resource error, got '%v'", invalidSuffix, err) |
| } |
| } |
| } |
| |
| func (t *Tester) testCreateInvokesValidation(opts metav1.CreateOptions, invalid ...runtime.Object) { |
| for i, obj := range invalid { |
| ctx := t.TestContext() |
| _, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, &opts) |
| if !errors.IsInvalid(err) { |
| t.Errorf("%d: Expected to get an invalid resource error, got %v", i, err) |
| } |
| } |
| } |
| |
| func (t *Tester) testCreateRejectsMismatchedNamespace(valid runtime.Object, opts metav1.CreateOptions) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| objectMeta.SetNamespace("not-default") |
| |
| _, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err == nil { |
| t.Errorf("Expected an error, but we didn't get one") |
| } else if !strings.Contains(err.Error(), "does not match the namespace sent on the request") { |
| t.Errorf("Expected 'does not match the namespace sent on the request' error, got '%v'", err.Error()) |
| } |
| } |
| |
| func (t *Tester) testCreateResetsUserData(valid runtime.Object, opts metav1.CreateOptions) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| now := metav1.Now() |
| objectMeta.SetUID("bad-uid") |
| objectMeta.SetCreationTimestamp(now) |
| |
| obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid, rest.ValidateAllObjectFunc, &opts) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| if obj == nil { |
| t.Fatalf("Unexpected object from result: %#v", obj) |
| } |
| defer t.delete(t.TestContext(), obj) |
| if objectMeta.GetUID() == "bad-uid" || objectMeta.GetCreationTimestamp() == now { |
| t.Errorf("ObjectMeta did not reset basic fields: %#v", objectMeta) |
| } |
| } |
| |
| func (t *Tester) testCreateIgnoreClusterName(valid runtime.Object, opts metav1.CreateOptions) { |
| objectMeta := t.getObjectMetaOrFail(valid) |
| objectMeta.SetName(t.namer(3)) |
| objectMeta.SetClusterName("clustername-to-ignore") |
| |
| obj, err := t.storage.(rest.Creater).Create(t.TestContext(), valid.DeepCopyObject(), rest.ValidateAllObjectFunc, &opts) |
| if err != nil { |
| t.Fatalf("Unexpected error: %v", err) |
| } |
| defer t.delete(t.TestContext(), obj) |
| createdObjectMeta := t.getObjectMetaOrFail(obj) |
| if len(createdObjectMeta.GetClusterName()) != 0 { |
| t.Errorf("Expected empty clusterName on created object, got '%v'", createdObjectMeta.GetClusterName()) |
| } |
| } |
| |
| // ============================================================================= |
| // Update tests. |
| |
| func (t *Tester) testUpdateEquals(obj runtime.Object, createFn CreateFunc, getFn GetFunc, updateFn UpdateFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(2)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| toUpdate, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| toUpdate = updateFn(toUpdate) |
| toUpdateMeta := t.getObjectMetaOrFail(toUpdate) |
| updated, created, err := t.storage.(rest.Updater).Update(ctx, toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if created { |
| t.Errorf("unexpected creation") |
| } |
| got, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| // Set resource version which might be unset in created object. |
| updatedMeta := t.getObjectMetaOrFail(updated) |
| gotMeta := t.getObjectMetaOrFail(got) |
| updatedMeta.SetResourceVersion(gotMeta.GetResourceVersion()) |
| |
| if e, a := updated, got; !apiequality.Semantic.DeepEqual(e, a) { |
| t.Errorf("unexpected obj: %#v, expected %#v", e, a) |
| } |
| } |
| |
| func (t *Tester) testUpdateFailsOnVersionTooOld(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(3)) |
| |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| storedFoo, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| older := storedFoo.DeepCopyObject() |
| olderMeta := t.getObjectMetaOrFail(older) |
| olderMeta.SetResourceVersion("1") |
| |
| _, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if err == nil { |
| t.Errorf("Expected an error, but we didn't get one") |
| } else if !errors.IsConflict(err) { |
| t.Errorf("Expected Conflict error, got '%v'", err) |
| } |
| } |
| |
| func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, createFn CreateFunc, invalidUpdateFn ...UpdateFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(4)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| for _, update := range invalidUpdateFn { |
| toUpdate := update(foo.DeepCopyObject()) |
| toUpdateMeta := t.getObjectMetaOrFail(toUpdate) |
| got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdateMeta.GetName(), rest.DefaultUpdatedObjectInfo(toUpdate), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if got != nil || created { |
| t.Errorf("expected nil object and no creation for object: %v", toUpdate) |
| } |
| if !errors.IsInvalid(err) && !errors.IsBadRequest(err) { |
| t.Errorf("expected invalid or bad request error, got %v", err) |
| } |
| } |
| } |
| |
| func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, createFn CreateFunc, getFn GetFunc, opts metav1.UpdateOptions) { |
| ctx := t.TestContext() |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(5)) |
| objectMeta := t.getObjectMetaOrFail(foo) |
| objectMeta.SetUID(types.UID("UID0000")) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| defer t.delete(ctx, foo) |
| objectMeta.SetUID(types.UID("UID1111")) |
| |
| obj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(foo), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &opts) |
| if created || obj != nil { |
| t.Errorf("expected nil object and no creation for object: %v", foo) |
| } |
| if err == nil || !errors.IsConflict(err) { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } |
| |
| func (t *Tester) testUpdateRetrievesOldObject(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { |
| ctx := t.TestContext() |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(6)) |
| objectMeta := t.getObjectMetaOrFail(foo) |
| objectMeta.SetAnnotations(map[string]string{"A": "1"}) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| return |
| } |
| |
| storedFoo, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| return |
| } |
| |
| storedFooWithUpdates := storedFoo.DeepCopyObject() |
| objectMeta = t.getObjectMetaOrFail(storedFooWithUpdates) |
| objectMeta.SetAnnotations(map[string]string{"A": "2"}) |
| |
| // Make sure a custom transform is called, and sees the expected updatedObject and oldObject |
| // This tests the mechanism used to pass the old and new object to admission |
| calledUpdatedObject := 0 |
| noopTransform := func(_ context.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) { |
| if !reflect.DeepEqual(storedFoo, oldObject) { |
| t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFoo, oldObject) |
| } |
| if !reflect.DeepEqual(storedFooWithUpdates, updatedObject) { |
| t.Errorf("Expected\n\t%#v\ngot\n\t%#v", storedFooWithUpdates, updatedObject) |
| } |
| calledUpdatedObject++ |
| return updatedObject, nil |
| } |
| |
| updatedObj, created, err := t.storage.(rest.Updater).Update(ctx, objectMeta.GetName(), rest.DefaultUpdatedObjectInfo(storedFooWithUpdates, noopTransform), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| return |
| } |
| if created { |
| t.Errorf("expected no creation for object") |
| return |
| } |
| if updatedObj == nil { |
| t.Errorf("expected non-nil object from update") |
| return |
| } |
| if calledUpdatedObject != 1 { |
| t.Errorf("expected UpdatedObject() to be called 1 time, was called %d", calledUpdatedObject) |
| return |
| } |
| } |
| |
| func (t *Tester) testUpdatePropagatesUpdatedObjectError(obj runtime.Object, createFn CreateFunc, getFn GetFunc, opts metav1.UpdateOptions) { |
| ctx := t.TestContext() |
| foo := obj.DeepCopyObject() |
| name := t.namer(7) |
| t.setObjectMeta(foo, name) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| return |
| } |
| defer t.delete(ctx, foo) |
| |
| // Make sure our transform is called, and sees the expected updatedObject and oldObject |
| propagateErr := fmt.Errorf("custom updated object error for %v", foo) |
| noopTransform := func(_ context.Context, updatedObject runtime.Object, oldObject runtime.Object) (runtime.Object, error) { |
| return nil, propagateErr |
| } |
| |
| _, _, err := t.storage.(rest.Updater).Update(ctx, name, rest.DefaultUpdatedObjectInfo(foo, noopTransform), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &opts) |
| if err != propagateErr { |
| t.Errorf("expected propagated error, got %#v", err) |
| } |
| } |
| |
| func (t *Tester) testUpdateIgnoreGenerationUpdates(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| name := t.namer(8) |
| t.setObjectMeta(foo, name) |
| |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| storedFoo, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| older := storedFoo.DeepCopyObject() |
| olderMeta := t.getObjectMetaOrFail(older) |
| olderMeta.SetGeneration(2) |
| |
| _, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if err != nil { |
| t.Errorf("Unexpected error: %v", err) |
| } |
| |
| updatedFoo, err := getFn(ctx, older) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if exp, got := int64(1), t.getObjectMetaOrFail(updatedFoo).GetGeneration(); exp != got { |
| t.Errorf("Unexpected generation update: expected %d, got %d", exp, got) |
| } |
| } |
| |
| func (t *Tester) testUpdateOnNotFound(obj runtime.Object, opts metav1.UpdateOptions) { |
| t.setObjectMeta(obj, t.namer(0)) |
| _, created, err := t.storage.(rest.Updater).Update(t.TestContext(), t.namer(0), rest.DefaultUpdatedObjectInfo(obj), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &opts) |
| if t.createOnUpdate { |
| if err != nil { |
| t.Errorf("creation allowed on updated, but got an error: %v", err) |
| } |
| if !created { |
| t.Errorf("creation allowed on update, but object not created") |
| } |
| } else { |
| if err == nil { |
| t.Errorf("Expected an error, but we didn't get one") |
| } else if !errors.IsNotFound(err) { |
| t.Errorf("Expected NotFound error, got '%v'", err) |
| } |
| } |
| } |
| |
| func (t *Tester) testUpdateRejectsMismatchedNamespace(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| storedFoo, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| objectMeta := t.getObjectMetaOrFail(storedFoo) |
| objectMeta.SetName(t.namer(1)) |
| objectMeta.SetNamespace("not-default") |
| |
| obj, updated, err := t.storage.(rest.Updater).Update(t.TestContext(), "foo1", rest.DefaultUpdatedObjectInfo(storedFoo), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if obj != nil || updated { |
| t.Errorf("expected nil object and not updated") |
| } |
| if err == nil { |
| t.Errorf("expected an error, but didn't get one") |
| } else if !strings.Contains(err.Error(), "does not match the namespace sent on the request") { |
| t.Errorf("expected 'does not match the namespace sent on the request' error, got '%v'", err.Error()) |
| } |
| } |
| |
| func (t *Tester) testUpdateIgnoreClusterName(obj runtime.Object, createFn CreateFunc, getFn GetFunc) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| name := t.namer(9) |
| t.setObjectMeta(foo, name) |
| |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| storedFoo, err := getFn(ctx, foo) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| older := storedFoo.DeepCopyObject() |
| olderMeta := t.getObjectMetaOrFail(older) |
| olderMeta.SetClusterName("clustername-to-ignore") |
| |
| _, _, err = t.storage.(rest.Updater).Update(t.TestContext(), olderMeta.GetName(), rest.DefaultUpdatedObjectInfo(older), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) |
| if err != nil { |
| t.Errorf("Unexpected error: %v", err) |
| } |
| |
| updatedFoo, err := getFn(ctx, older) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if clusterName := t.getObjectMetaOrFail(updatedFoo).GetClusterName(); len(clusterName) != 0 { |
| t.Errorf("Unexpected clusterName update: expected empty, got %v", clusterName) |
| } |
| |
| } |
| |
| // ============================================================================= |
| // Deletion tests. |
| |
| func (t *Tester) testDeleteNoGraceful(obj runtime.Object, createFn CreateFunc, getFn GetFunc, isNotFoundFn IsErrorFunc, dryRun bool) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| defer t.delete(ctx, foo) |
| objectMeta := t.getObjectMetaOrFail(foo) |
| opts := metav1.NewDeleteOptions(10) |
| if dryRun { |
| opts.DryRun = []string{metav1.DryRunAll} |
| } |
| obj, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), opts) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if !wasDeleted { |
| t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.GetName()) |
| } |
| if !t.returnDeletedObject { |
| if status, ok := obj.(*metav1.Status); !ok { |
| t.Errorf("expected status of delete, got %v", status) |
| } else if status.Status != metav1.StatusSuccess { |
| t.Errorf("expected success, got: %v", status.Status) |
| } |
| } |
| |
| _, err = getFn(ctx, foo) |
| if !dryRun && (err == nil || !isNotFoundFn(err)) { |
| t.Errorf("unexpected error: %v", err) |
| } else if dryRun && isNotFoundFn(err) { |
| t.Error("object should not have been removed in dry-run") |
| } |
| } |
| |
| func (t *Tester) testDeleteNonExist(obj runtime.Object, opts metav1.DeleteOptions) { |
| objectMeta := t.getObjectMetaOrFail(obj) |
| |
| _, _, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.GetName(), &opts) |
| if err == nil || !errors.IsNotFound(err) { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| } |
| |
| // This test the fast-fail path. We test that the precondition gets verified |
| // again before deleting the object in tests of pkg/storage/etcd. |
| func (t *Tester) testDeleteWithUID(obj runtime.Object, createFn CreateFunc, getFn GetFunc, isNotFoundFn IsErrorFunc, opts metav1.DeleteOptions) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| objectMeta := t.getObjectMetaOrFail(foo) |
| objectMeta.SetUID(types.UID("UID0000")) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| opts.Preconditions = metav1.NewPreconditionDeleteOptions("UID1111").Preconditions |
| obj, _, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &opts) |
| if err == nil || !errors.IsConflict(err) { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| obj, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewPreconditionDeleteOptions("UID0000")) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| if !t.returnDeletedObject { |
| if status, ok := obj.(*metav1.Status); !ok { |
| t.Errorf("expected status of delete, got %v", status) |
| } else if status.Status != metav1.StatusSuccess { |
| t.Errorf("expected success, got: %v", status.Status) |
| } |
| } |
| |
| _, err = getFn(ctx, foo) |
| if err == nil || !isNotFoundFn(err) { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } |
| |
| // ============================================================================= |
| // Graceful Deletion tests. |
| |
| func (t *Tester) testDeleteDryRunGracefulHasdefault(obj runtime.Object, createFn CreateFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| defer t.delete(ctx, foo) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| object, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &metav1.DeleteOptions{DryRun: []string{metav1.DryRunAll}}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() != expectedGrace { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| _, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &metav1.DeleteOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } |
| |
| func (t *Tester) testDeleteGracefulHasDefault(obj runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(1)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| generation := objectMeta.GetGeneration() |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &metav1.DeleteOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| if _, err := getFn(ctx, foo); err != nil { |
| t.Fatalf("did not gracefully delete resource: %v", err) |
| } |
| |
| object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Fatalf("unexpected error, object should exist: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() != expectedGrace { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| if generation >= objectMeta.GetGeneration() { |
| t.Error("Generation wasn't bumped when deletion timestamp was set") |
| } |
| } |
| |
| func (t *Tester) testDeleteGracefulWithValue(obj runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(2)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| generation := objectMeta.GetGeneration() |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace+2)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| if _, err := getFn(ctx, foo); err != nil { |
| t.Fatalf("did not gracefully delete resource: %v", err) |
| } |
| |
| object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error, object should exist: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() != expectedGrace+2 { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| if generation >= objectMeta.GetGeneration() { |
| t.Error("Generation wasn't bumped when deletion timestamp was set") |
| } |
| } |
| |
| func (t *Tester) testDeleteGracefulExtend(obj runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(3)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| generation := objectMeta.GetGeneration() |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| if _, err := getFn(ctx, foo); err != nil { |
| t.Fatalf("did not gracefully delete resource: %v", err) |
| } |
| |
| // second delete duration is ignored |
| _, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace+2)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error, object should exist: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() != expectedGrace { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| if generation >= objectMeta.GetGeneration() { |
| t.Error("Generation wasn't bumped when deletion timestamp was set") |
| } |
| } |
| |
| func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, "foo4") |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| generation := objectMeta.GetGeneration() |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| if _, err := getFn(ctx, foo); err != nil { |
| t.Fatalf("did not gracefully delete resource: %v", err) |
| } |
| |
| // second delete is immediate, resource is deleted |
| out, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(0)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted != true { |
| t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.GetName()) |
| } |
| _, err = t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}) |
| if !errors.IsNotFound(err) { |
| t.Errorf("unexpected error, object should be deleted immediately: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(out) |
| // the second delete shouldn't update the object, so the objectMeta.GetDeletionGracePeriodSeconds() should eqaul to the value set in the first delete. |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || *objectMeta.GetDeletionGracePeriodSeconds() != 0 { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| if generation >= objectMeta.GetGeneration() { |
| t.Error("Generation wasn't bumped when deletion timestamp was set") |
| } |
| } |
| |
| func (t *Tester) testDeleteGracefulUsesZeroOnNil(obj runtime.Object, createFn CreateFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(5)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), nil) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if !wasDeleted { |
| t.Errorf("unexpected, object %s should have been deleted immediately", objectMeta.GetName()) |
| } |
| if _, err := t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}); !errors.IsNotFound(err) { |
| t.Errorf("unexpected error, object should not exist: %v", err) |
| } |
| } |
| |
| // Regression test for bug discussed in #27539 |
| func (t *Tester) testDeleteGracefulShorten(obj runtime.Object, createFn CreateFunc, getFn GetFunc, expectedGrace int64) { |
| ctx := t.TestContext() |
| |
| foo := obj.DeepCopyObject() |
| t.setObjectMeta(foo, t.namer(6)) |
| if err := createFn(ctx, foo); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| bigGrace := int64(time.Hour) |
| if expectedGrace > bigGrace { |
| bigGrace = 2 * expectedGrace |
| } |
| objectMeta := t.getObjectMetaOrFail(foo) |
| _, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(bigGrace)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| object, err := getFn(ctx, foo) |
| if err != nil { |
| t.Fatalf("did not gracefully delete resource: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| deletionTimestamp := *objectMeta.GetDeletionTimestamp() |
| |
| // second delete duration is ignored |
| _, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace)) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if wasDeleted { |
| t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName()) |
| } |
| object, err = t.storage.(rest.Getter).Get(ctx, objectMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error, object should exist: %v", err) |
| } |
| objectMeta = t.getObjectMetaOrFail(object) |
| if objectMeta.GetDeletionTimestamp() == nil || objectMeta.GetDeletionGracePeriodSeconds() == nil || |
| *objectMeta.GetDeletionGracePeriodSeconds() != expectedGrace || !objectMeta.GetDeletionTimestamp().Before(&deletionTimestamp) { |
| t.Errorf("unexpected deleted meta: %#v", objectMeta) |
| } |
| } |
| |
| // ============================================================================= |
| // Get tests. |
| |
| // testGetDifferentNamespace ensures same-name objects in different namespaces do not clash |
| func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { |
| if t.clusterScope { |
| t.Fatalf("the test does not work in cluster-scope") |
| } |
| |
| objMeta := t.getObjectMetaOrFail(obj) |
| objMeta.SetName(t.namer(5)) |
| |
| ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar3") |
| objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) |
| _, err := t.storage.(rest.Creater).Create(ctx1, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar4") |
| objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx2)) |
| _, err = t.storage.(rest.Creater).Create(ctx2, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| got1, err := t.storage.(rest.Getter).Get(ctx1, objMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| got1Meta := t.getObjectMetaOrFail(got1) |
| if got1Meta.GetName() != objMeta.GetName() { |
| t.Errorf("unexpected name of object: %#v, expected: %s", got1, objMeta.GetName()) |
| } |
| if got1Meta.GetNamespace() != genericapirequest.NamespaceValue(ctx1) { |
| t.Errorf("unexpected namespace of object: %#v, expected: %s", got1, genericapirequest.NamespaceValue(ctx1)) |
| } |
| |
| got2, err := t.storage.(rest.Getter).Get(ctx2, objMeta.GetName(), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| got2Meta := t.getObjectMetaOrFail(got2) |
| if got2Meta.GetName() != objMeta.GetName() { |
| t.Errorf("unexpected name of object: %#v, expected: %s", got2, objMeta.GetName()) |
| } |
| if got2Meta.GetNamespace() != genericapirequest.NamespaceValue(ctx2) { |
| t.Errorf("unexpected namespace of object: %#v, expected: %s", got2, genericapirequest.NamespaceValue(ctx2)) |
| } |
| } |
| |
| func (t *Tester) testGetFound(obj runtime.Object) { |
| ctx := t.TestContext() |
| t.setObjectMeta(obj, t.namer(1)) |
| |
| existing, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| existingMeta := t.getObjectMetaOrFail(existing) |
| |
| got, err := t.storage.(rest.Getter).Get(ctx, t.namer(1), &metav1.GetOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| gotMeta := t.getObjectMetaOrFail(got) |
| gotMeta.SetResourceVersion(existingMeta.GetResourceVersion()) |
| if e, a := existing, got; !apiequality.Semantic.DeepEqual(e, a) { |
| t.Errorf("unexpected obj: %#v, expected %#v", e, a) |
| } |
| } |
| |
| func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { |
| ctx1 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar1") |
| ctx2 := genericapirequest.WithNamespace(genericapirequest.NewContext(), "bar2") |
| objMeta := t.getObjectMetaOrFail(obj) |
| objMeta.SetName(t.namer(4)) |
| objMeta.SetNamespace(genericapirequest.NamespaceValue(ctx1)) |
| _, err := t.storage.(rest.Creater).Create(ctx1, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| _, err = t.storage.(rest.Getter).Get(ctx2, t.namer(4), &metav1.GetOptions{}) |
| if t.clusterScope { |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } else { |
| if !errors.IsNotFound(err) { |
| t.Errorf("unexpected error returned: %#v", err) |
| } |
| } |
| } |
| |
| func (t *Tester) testGetNotFound(obj runtime.Object) { |
| ctx := t.TestContext() |
| t.setObjectMeta(obj, t.namer(2)) |
| _, err := t.storage.(rest.Creater).Create(ctx, obj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| _, err = t.storage.(rest.Getter).Get(ctx, t.namer(3), &metav1.GetOptions{}) |
| if !errors.IsNotFound(err) { |
| t.Errorf("unexpected error returned: %#v", err) |
| } |
| } |
| |
| // ============================================================================= |
| // List tests. |
| |
| func listToItems(listObj runtime.Object) ([]runtime.Object, error) { |
| v, err := conversion.EnforcePtr(listObj) |
| if err != nil { |
| return nil, fmt.Errorf("unexpected error: %v", err) |
| } |
| items := v.FieldByName("Items") |
| if !items.IsValid() { |
| return nil, fmt.Errorf("unexpected Items field in %v", listObj) |
| } |
| if items.Type().Kind() != reflect.Slice { |
| return nil, fmt.Errorf("unexpected Items field type: %v", items.Type().Kind()) |
| } |
| result := make([]runtime.Object, items.Len()) |
| for i := 0; i < items.Len(); i++ { |
| result[i] = items.Index(i).Addr().Interface().(runtime.Object) |
| } |
| return result, nil |
| } |
| |
| func (t *Tester) testListFound(obj runtime.Object, assignFn AssignFunc) { |
| ctx := t.TestContext() |
| |
| foo1 := obj.DeepCopyObject() |
| t.setObjectMeta(foo1, t.namer(1)) |
| foo2 := obj.DeepCopyObject() |
| t.setObjectMeta(foo2, t.namer(2)) |
| |
| existing := assignFn([]runtime.Object{foo1, foo2}) |
| |
| listObj, err := t.storage.(rest.Lister).List(ctx, nil) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| items, err := listToItems(listObj) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if len(items) != len(existing) { |
| t.Errorf("unexpected number of items: %v", len(items)) |
| } |
| if !apiequality.Semantic.DeepEqual(existing, items) { |
| t.Errorf("expected: %#v, got: %#v", existing, items) |
| } |
| } |
| |
| func (t *Tester) testListMatchLabels(obj runtime.Object, assignFn AssignFunc) { |
| ctx := t.TestContext() |
| testLabels := map[string]string{"key": "value"} |
| |
| foo3 := obj.DeepCopyObject() |
| t.setObjectMeta(foo3, "foo3") |
| foo4 := obj.DeepCopyObject() |
| foo4Meta := t.getObjectMetaOrFail(foo4) |
| foo4Meta.SetName("foo4") |
| foo4Meta.SetNamespace(genericapirequest.NamespaceValue(ctx)) |
| foo4Meta.SetLabels(testLabels) |
| |
| objs := ([]runtime.Object{foo3, foo4}) |
| |
| assignFn(objs) |
| filtered := []runtime.Object{objs[1]} |
| |
| selector := labels.SelectorFromSet(labels.Set(testLabels)) |
| options := &metainternalversion.ListOptions{LabelSelector: selector} |
| listObj, err := t.storage.(rest.Lister).List(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| items, err := listToItems(listObj) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if len(items) != len(filtered) { |
| t.Errorf("unexpected number of items: %v", len(items)) |
| } |
| if !apiequality.Semantic.DeepEqual(filtered, items) { |
| t.Errorf("expected: %#v, got: %#v", filtered, items) |
| } |
| } |
| |
| func (t *Tester) testListNotFound(assignFn AssignFunc) { |
| ctx := t.TestContext() |
| _ = assignFn([]runtime.Object{}) |
| |
| listObj, err := t.storage.(rest.Lister).List(ctx, nil) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| items, err := listToItems(listObj) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if len(items) != 0 { |
| t.Errorf("unexpected items: %#v", items) |
| } |
| } |
| |
| // testListTableConversion verifies a set of known bounds and expected limitations for the values |
| // returned from a TableList. These conditions may be changed if necessary with adequate review. |
| func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc) { |
| ctx := t.TestContext() |
| testLabels := map[string]string{"key": "value"} |
| |
| foo3 := obj.DeepCopyObject() |
| t.setObjectMeta(foo3, "foo3") |
| foo4 := obj.DeepCopyObject() |
| foo4Meta := t.getObjectMetaOrFail(foo4) |
| foo4Meta.SetName("foo4") |
| foo4Meta.SetNamespace(genericapirequest.NamespaceValue(ctx)) |
| foo4Meta.SetLabels(testLabels) |
| |
| objs := ([]runtime.Object{foo3, foo4}) |
| |
| assignFn(objs) |
| |
| options := &metainternalversion.ListOptions{} |
| listObj, err := t.storage.(rest.Lister).List(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| items, err := listToItems(listObj) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if len(items) != len(objs) { |
| t.Errorf("unexpected number of items: %v", len(items)) |
| } |
| if !apiequality.Semantic.DeepEqual(objs, items) { |
| t.Errorf("expected: %#v, got: %#v", objs, items) |
| } |
| |
| m, err := meta.ListAccessor(listObj) |
| if err != nil { |
| t.Fatalf("list should support ListMeta %T: %v", listObj, err) |
| } |
| m.SetContinue("continuetoken") |
| m.SetResourceVersion("11") |
| m.SetSelfLink("/list/link") |
| |
| table, err := t.storage.(rest.TableConvertor).ConvertToTable(ctx, listObj, nil) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if table.ResourceVersion != "11" || table.SelfLink != "/list/link" || table.Continue != "continuetoken" { |
| t.Errorf("printer lost list meta: %#v", table.ListMeta) |
| } |
| if len(table.Rows) != len(items) { |
| t.Errorf("unexpected number of rows: %v", len(table.Rows)) |
| } |
| columns := table.ColumnDefinitions |
| if len(columns) == 0 { |
| t.Errorf("unexpected number of columns: %v", len(columns)) |
| } |
| if !strings.EqualFold(columns[0].Name, "Name") || columns[0].Type != "string" || columns[0].Format != "name" { |
| t.Errorf("expect column 0 to be the name column: %#v", columns[0]) |
| } |
| for j, column := range columns { |
| if len(column.Name) == 0 { |
| t.Errorf("column %d has no name", j) |
| } |
| switch column.Type { |
| case "string", "date", "integer", "number", "boolean": |
| default: |
| t.Errorf("column %d has unexpected type: %q", j, column.Type) |
| } |
| switch { |
| case column.Format == "": |
| case column.Format == "name" && column.Type == "string": |
| default: |
| t.Errorf("column %d has unexpected format: %q with type %q", j, column.Format, column.Type) |
| } |
| if column.Priority < 0 || column.Priority > 2 { |
| t.Errorf("column %d has unexpected priority: %q", j, column.Priority) |
| } |
| if len(column.Description) == 0 { |
| t.Errorf("column %d has no description", j) |
| } |
| if column.Name == "Created At" && column.Type != "date" && column.Format != "" { |
| t.Errorf("column %d looks like a created at column, but has a different type and format: %#v", j, column) |
| } |
| } |
| for i, row := range table.Rows { |
| if len(row.Cells) != len(table.ColumnDefinitions) { |
| t.Errorf("row %d did not have the correct number of cells: %d in %v, expected %d", i, len(row.Cells), row.Cells, len(table.ColumnDefinitions)) |
| } |
| for j, cell := range row.Cells { |
| // do not add to this test without discussion - may break clients |
| switch cell.(type) { |
| case float64, int64, int32, int, string, bool: |
| case []interface{}: |
| case nil: |
| default: |
| t.Errorf("row %d, cell %d has an unrecognized type, only JSON serialization safe types are allowed: %T ", i, j, cell) |
| } |
| } |
| if len(row.Cells) != len(table.ColumnDefinitions) { |
| } |
| } |
| } |
| |
| // ============================================================================= |
| // Watching tests. |
| |
| func (t *Tester) testWatchFields(obj runtime.Object, emitFn EmitFunc, fieldsPass, fieldsFail []fields.Set, actions []string) { |
| ctx := t.TestContext() |
| |
| for _, field := range fieldsPass { |
| for _, action := range actions { |
| options := &metainternalversion.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} |
| watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v, %v", err, action) |
| } |
| |
| if err := emitFn(obj, action); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| select { |
| case _, ok := <-watcher.ResultChan(): |
| if !ok { |
| t.Errorf("watch channel should be open") |
| } |
| case <-time.After(wait.ForeverTestTimeout): |
| t.Errorf("unexpected timeout from result channel") |
| } |
| watcher.Stop() |
| } |
| } |
| |
| for _, field := range fieldsFail { |
| for _, action := range actions { |
| options := &metainternalversion.ListOptions{FieldSelector: field.AsSelector(), ResourceVersion: "1"} |
| watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if err := emitFn(obj, action); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| select { |
| case <-watcher.ResultChan(): |
| t.Errorf("unexpected result from result channel") |
| case <-time.After(time.Millisecond * 500): |
| // expected case |
| } |
| watcher.Stop() |
| } |
| } |
| } |
| |
| func (t *Tester) testWatchLabels(obj runtime.Object, emitFn EmitFunc, labelsPass, labelsFail []labels.Set, actions []string) { |
| ctx := t.TestContext() |
| |
| for _, label := range labelsPass { |
| for _, action := range actions { |
| options := &metainternalversion.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} |
| watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if err := emitFn(obj, action); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| select { |
| case _, ok := <-watcher.ResultChan(): |
| if !ok { |
| t.Errorf("watch channel should be open") |
| } |
| case <-time.After(wait.ForeverTestTimeout): |
| t.Errorf("unexpected timeout from result channel") |
| } |
| watcher.Stop() |
| } |
| } |
| |
| for _, label := range labelsFail { |
| for _, action := range actions { |
| options := &metainternalversion.ListOptions{LabelSelector: label.AsSelector(), ResourceVersion: "1"} |
| watcher, err := t.storage.(rest.Watcher).Watch(ctx, options) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| if err := emitFn(obj, action); err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| |
| select { |
| case <-watcher.ResultChan(): |
| t.Errorf("unexpected result from result channel") |
| case <-time.After(time.Millisecond * 500): |
| // expected case |
| } |
| watcher.Stop() |
| } |
| } |
| } |