From 526d12c09bb40c3b0aa98182acf96eeca4e68898 Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 10 Mar 2021 15:41:03 -0500 Subject: [PATCH] prevent mutation of deletion options during delete collection Kubernetes-commit: 649b87aaf85dbb6e8190bf7d16c5dc903b5ecedc --- pkg/registry/generic/registry/store.go | 7 +++- pkg/registry/generic/registry/store_test.go | 40 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/pkg/registry/generic/registry/store.go b/pkg/registry/generic/registry/store.go index 4ff379aef..7a626855a 100644 --- a/pkg/registry/generic/registry/store.go +++ b/pkg/registry/generic/registry/store.go @@ -982,6 +982,7 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name } // Delete removes the item from storage. +// options can be mutated by rest.BeforeDelete due to a graceful deletion strategy. func (e *Store) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { key, err := e.KeyFunc(ctx, name) if err != nil { @@ -1148,7 +1149,11 @@ func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.Vali errs <- err return } - if _, _, err := e.Delete(ctx, accessor.GetName(), deleteValidation, options); err != nil && !apierrors.IsNotFound(err) { + // DeepCopy the deletion options because individual graceful deleters communicate changes via a mutating + // function in the delete strategy called in the delete method. While that is always ugly, it works + // when making a single call. When making multiple calls via delete collection, the mutation applied to + // pod/A can change the option ultimately used for pod/B. + if _, _, err := e.Delete(ctx, accessor.GetName(), deleteValidation, options.DeepCopy()); err != nil && !apierrors.IsNotFound(err) { klog.V(4).InfoS("Delete object in DeleteCollection failed", "object", klog.KObj(accessor), "err", err) errs <- err return diff --git a/pkg/registry/generic/registry/store_test.go b/pkg/registry/generic/registry/store_test.go index c6a9a543c..74d57b185 100644 --- a/pkg/registry/generic/registry/store_test.go +++ b/pkg/registry/generic/registry/store_test.go @@ -86,6 +86,16 @@ func (t *testOrphanDeleteStrategy) DefaultGarbageCollectionPolicy(ctx context.Co return rest.OrphanDependents } +type mutatingDeleteRESTStrategy struct { + runtime.ObjectTyper +} + +func (t *mutatingDeleteRESTStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool { + n := int64(10) + options.GracePeriodSeconds = &n + return true +} + type testRESTStrategy struct { runtime.ObjectTyper names.NameGenerator @@ -2041,6 +2051,36 @@ func TestStoreDeleteCollection(t *testing.T) { } } +func TestStoreDeleteCollectionNoMutateOptions(t *testing.T) { + podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + podB := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} + + testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") + destroyFunc, registry := NewTestGenericStoreRegistry(t) + registry.DeleteStrategy = &mutatingDeleteRESTStrategy{scheme} + defer destroyFunc() + + if _, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + n := int64(50) + inputDeleteOptions := &metav1.DeleteOptions{GracePeriodSeconds: &n} + safeCopyOfDelete := inputDeleteOptions.DeepCopy() + // Delete all pods. + _, err := registry.DeleteCollection(testContext, rest.ValidateAllObjectFunc, inputDeleteOptions, &metainternalversion.ListOptions{}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if !reflect.DeepEqual(inputDeleteOptions, safeCopyOfDelete) { + t.Error(inputDeleteOptions) + } +} + func TestStoreDeleteCollectionNotFound(t *testing.T) { destroyFunc, registry := NewTestGenericStoreRegistry(t) defer destroyFunc()