From 2670651a3c06028b6aaadb9dc7151d71357364bf Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Tue, 3 Sep 2019 17:09:12 -0700 Subject: [PATCH 1/2] test --- .../registry/generic/registry/store_test.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) 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 e498afc4a06..1e8da623704 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 @@ -1899,6 +1899,54 @@ func TestDeleteWithCachedObject(t *testing.T) { } } +func TestPreconditionalUpdateWithCachedObject(t *testing.T) { + podName := "foo" + pod := &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 has old UID + oldPod, err := registry.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + registry.Storage.Storage = &staleGuaranteedUpdateStorage{Interface: registry.Storage.Storage, cachedObj: oldPod} + + // delete and re-create the same object with new UID + _, _, err = registry.Delete(ctx, podName, rest.ValidateAllObjectFunc, nil) + if err != nil { + t.Fatal(err) + } + obj, err := registry.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + newPod, ok := obj.(*example.Pod) + if !ok { + t.Fatalf("unexpected object: %#v", obj) + } + + // update the object should not fail precondition + newPod.Spec.NodeName = "machine2" + res, _, err := registry.Update(ctx, podName, rest.DefaultUpdatedObjectInfo(newPod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) + if err != nil { + t.Fatal(err) + } + + // the update should have succeeded + r, ok := res.(*example.Pod) + if !ok { + t.Fatalf("unexpected update result: %#v", res) + } + if r.Spec.NodeName != "machine2" { + t.Fatalf("unexpected, update didn't take effect: %#v", r) + } +} + // TestRetryDeleteValidation checks if the deleteValidation is called again if // the GuaranteedUpdate in the Delete handler conflicts with a simultaneous // Update. From 88f0be6e59aa56570deaa2a9163d0eb3a3ae20f8 Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Tue, 3 Sep 2019 17:26:39 -0700 Subject: [PATCH 2/2] in GuaranteedUpdate, retry on precondition check failure if we are working with cached data --- .../k8s.io/apiserver/pkg/storage/etcd3/store.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 425590aa886..f065872b52b 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go @@ -274,7 +274,20 @@ func (s *store) GuaranteedUpdate( transformContext := authenticatedDataString(key) for { if err := preconditions.Check(key, origState.obj); err != nil { - return err + // If our data is already up to date, return the error + if !mustCheckData { + 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 } ret, ttl, err := s.updateState(origState, tryUpdate)