Skip to content

Commit 26a381b

Browse files
authored
Merge pull request kubernetes#82303 from roycaihw/update-precondition-retry
In GuaranteedUpdate, retry on a precondition check failure if we are working with cached data
2 parents 6cb788c + 88f0be6 commit 26a381b

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,54 @@ func TestDeleteWithCachedObject(t *testing.T) {
18991899
}
19001900
}
19011901

1902+
func TestPreconditionalUpdateWithCachedObject(t *testing.T) {
1903+
podName := "foo"
1904+
pod := &example.Pod{
1905+
ObjectMeta: metav1.ObjectMeta{Name: podName},
1906+
Spec: example.PodSpec{NodeName: "machine"},
1907+
}
1908+
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
1909+
destroyFunc, registry := newTestGenericStoreRegistry(t, scheme, false)
1910+
defer destroyFunc()
1911+
1912+
// cached object has old UID
1913+
oldPod, err := registry.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
1914+
if err != nil {
1915+
t.Fatal(err)
1916+
}
1917+
registry.Storage.Storage = &staleGuaranteedUpdateStorage{Interface: registry.Storage.Storage, cachedObj: oldPod}
1918+
1919+
// delete and re-create the same object with new UID
1920+
_, _, err = registry.Delete(ctx, podName, rest.ValidateAllObjectFunc, nil)
1921+
if err != nil {
1922+
t.Fatal(err)
1923+
}
1924+
obj, err := registry.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
1925+
if err != nil {
1926+
t.Fatal(err)
1927+
}
1928+
newPod, ok := obj.(*example.Pod)
1929+
if !ok {
1930+
t.Fatalf("unexpected object: %#v", obj)
1931+
}
1932+
1933+
// update the object should not fail precondition
1934+
newPod.Spec.NodeName = "machine2"
1935+
res, _, err := registry.Update(ctx, podName, rest.DefaultUpdatedObjectInfo(newPod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
1936+
if err != nil {
1937+
t.Fatal(err)
1938+
}
1939+
1940+
// the update should have succeeded
1941+
r, ok := res.(*example.Pod)
1942+
if !ok {
1943+
t.Fatalf("unexpected update result: %#v", res)
1944+
}
1945+
if r.Spec.NodeName != "machine2" {
1946+
t.Fatalf("unexpected, update didn't take effect: %#v", r)
1947+
}
1948+
}
1949+
19021950
// TestRetryDeleteValidation checks if the deleteValidation is called again if
19031951
// the GuaranteedUpdate in the Delete handler conflicts with a simultaneous
19041952
// Update.

staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,20 @@ func (s *store) GuaranteedUpdate(
274274
transformContext := authenticatedDataString(key)
275275
for {
276276
if err := preconditions.Check(key, origState.obj); err != nil {
277-
return err
277+
// If our data is already up to date, return the error
278+
if !mustCheckData {
279+
return err
280+
}
281+
282+
// It's possible we were working with stale data
283+
// Actually fetch
284+
origState, err = getCurrentState()
285+
if err != nil {
286+
return err
287+
}
288+
mustCheckData = false
289+
// Retry
290+
continue
278291
}
279292

280293
ret, ttl, err := s.updateState(origState, tryUpdate)

0 commit comments

Comments
 (0)