From 5e53522a9ef56f33d06c457c072c379131e127ef Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Wed, 8 May 2019 15:05:07 -0700 Subject: [PATCH] In GuaranteedUpdate, retry on any error if we are working with stale data --- .../registry/generic/registry/store_test.go | 44 +++++++++++++++++++ .../apiserver/pkg/storage/etcd3/store.go | 23 +++++----- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go index d8c0c62cf10a..7b16f614e5b1 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go @@ -1851,3 +1851,47 @@ func TestMarkAsDeleting(t *testing.T) { }) } } + +type staleGuaranteedUpdateStorage struct { + storage.Interface + cachedObj runtime.Object +} + +// GuaranteedUpdate overwrites the method with one that always suggests the cachedObj. +func (s *staleGuaranteedUpdateStorage) GuaranteedUpdate( + ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, + preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, _ ...runtime.Object) error { + return s.Interface.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, preconditions, tryUpdate, s.cachedObj) +} + +func TestDeleteWithCachedObject(t *testing.T) { + podName := "foo" + podWithFinalizer := &example.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: podName, Finalizers: []string{"foo.com/x"}}, + Spec: example.PodSpec{NodeName: "machine"}, + } + podWithNoFinalizer := &example.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: podName}, + Spec: example.PodSpec{NodeName: "machine"}, + } + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test") + destroyFunc, registry := newTestGenericStoreRegistry(t, scheme, false) + defer destroyFunc() + // cached object does not have any finalizer. + registry.Storage.Storage = &staleGuaranteedUpdateStorage{Interface: registry.Storage.Storage, cachedObj: podWithNoFinalizer} + // created object with pending finalizer. + _, err := registry.Create(ctx, podWithFinalizer, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + // The object shouldn't be deleted, because the persisted object has pending finalizers. + _, _, err = registry.Delete(ctx, podName, nil) + if err != nil { + t.Fatal(err) + } + // The object should still be there + _, err = registry.Get(ctx, podName, &metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go index 1216b1f1e272..5a349c7a3ebd 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go @@ -301,19 +301,20 @@ func (s *store) GuaranteedUpdate( ret, ttl, err := s.updateState(origState, tryUpdate) if err != nil { - // It's possible we were working with stale data - if mustCheckData && apierrors.IsConflict(err) { - // Actually fetch - origState, err = getCurrentState() - if err != nil { - return err - } - mustCheckData = false - // Retry - continue + // If our data is already up to date, return the error + if !mustCheckData { + return err } - return err + // It's possible we were working with stale data + // Actually fetch + origState, err = getCurrentState() + if err != nil { + return err + } + mustCheckData = false + // Retry + continue } data, err := runtime.Encode(s.codec, ret)