Skip to content

Commit f75d475

Browse files
authored
Merge pull request #197 from takirala/tga/add-retry
fix(release-0.2): add retry during status updates
2 parents 9e82d56 + 4b3a334 commit f75d475

File tree

5 files changed

+257
-5
lines changed

5 files changed

+257
-5
lines changed

controller/pkg/bucketclaim/bucketclaim.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"k8s.io/apimachinery/pkg/runtime"
1111
kubeclientset "k8s.io/client-go/kubernetes"
1212
"k8s.io/client-go/tools/record"
13+
"k8s.io/client-go/util/retry"
1314
"k8s.io/klog/v2"
1415
"sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha1"
1516
bucketclientset "sigs.k8s.io/container-object-storage-interface/client/clientset/versioned"
@@ -216,18 +217,66 @@ func (b *BucketClaimListener) provisionBucketClaimOperation(ctx context.Context,
216217
bucketClaim.Status.BucketReady = false
217218
}
218219

219-
// Fetching the updated bucketClaim again, so that the update
220-
// operation doesn't happen on an outdated version of the bucketClaim.
221-
bucketClaim, err = b.bucketClaims(bucketClaim.ObjectMeta.Namespace).UpdateStatus(ctx, bucketClaim, metav1.UpdateOptions{})
220+
// Update status with retry logic for conflict errors
221+
// Store the status values we want to set before entering retry loop
222+
statusBucketName := bucketClaim.Status.BucketName
223+
statusBucketReady := bucketClaim.Status.BucketReady
224+
225+
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
226+
// Fetch the latest version of the BucketClaim
227+
latest, getErr := b.bucketClaims(bucketClaim.Namespace).Get(
228+
ctx,
229+
bucketClaim.Name,
230+
metav1.GetOptions{},
231+
)
232+
if getErr != nil {
233+
return getErr
234+
}
235+
236+
// Apply the status changes to the latest version
237+
latest.Status.BucketName = statusBucketName
238+
latest.Status.BucketReady = statusBucketReady
239+
240+
// Try to update the status
241+
var updateErr error
242+
bucketClaim, updateErr = b.bucketClaims(bucketClaim.Namespace).UpdateStatus(
243+
ctx,
244+
latest,
245+
metav1.UpdateOptions{},
246+
)
247+
return updateErr
248+
})
222249
if err != nil {
223250
klog.V(3).ErrorS(err, "Failed to update status of BucketClaim", "name", bucketClaim.ObjectMeta.Name)
224251
return b.recordError(inputBucketClaim, v1.EventTypeWarning, v1alpha1.FailedCreateBucket, err)
225252
}
226253

227254
// Add the finalizers so that bucketClaim is deleted
228255
// only after the associated bucket is deleted.
229-
controllerutil.AddFinalizer(bucketClaim, util.BucketClaimFinalizer)
230-
_, err = b.bucketClaims(bucketClaim.ObjectMeta.Namespace).Update(ctx, bucketClaim, metav1.UpdateOptions{})
256+
// Update with retry logic for conflict errors
257+
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
258+
// Fetch the latest version of the BucketClaim
259+
latest, getErr := b.bucketClaims(bucketClaim.Namespace).Get(
260+
ctx,
261+
bucketClaim.Name,
262+
metav1.GetOptions{},
263+
)
264+
if getErr != nil {
265+
return getErr
266+
}
267+
268+
// Add the finalizer to the latest version
269+
controllerutil.AddFinalizer(latest, util.BucketClaimFinalizer)
270+
271+
// Try to update
272+
var updateErr error
273+
bucketClaim, updateErr = b.bucketClaims(bucketClaim.Namespace).Update(
274+
ctx,
275+
latest,
276+
metav1.UpdateOptions{},
277+
)
278+
return updateErr
279+
})
231280
if err != nil {
232281
klog.V(3).ErrorS(err, "Failed to add finalizer BucketClaim", "name", bucketClaim.ObjectMeta.Name)
233282
return b.recordError(inputBucketClaim, v1.EventTypeWarning, v1alpha1.FailedCreateBucket, err)

controller/pkg/bucketclaim/bucketclaim_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bucketclaim
33
import (
44
"context"
55
"fmt"
6+
"sync"
67
"testing"
78

89
v1 "k8s.io/api/core/v1"
@@ -13,6 +14,7 @@ import (
1314
"sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha1"
1415
fakebucketclientset "sigs.k8s.io/container-object-storage-interface/client/clientset/versioned/fake"
1516
"sigs.k8s.io/container-object-storage-interface/controller/pkg/util"
17+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1618
)
1719

1820
var classGoldParameters = map[string]string{
@@ -352,3 +354,94 @@ func TestAddDeletedBucketClaim(t *testing.T) {
352354
t.Fatalf("expected 0 buckets, got %d", len(bl.Items))
353355
}
354356
}
357+
358+
// Test retry logic for conflict errors during status update
359+
func TestRetryOnConflictStatusUpdate(t *testing.T) {
360+
ctx, cancel := context.WithCancel(context.Background())
361+
defer cancel()
362+
363+
client := fakebucketclientset.NewSimpleClientset()
364+
kubeClient := fakekubeclientset.NewSimpleClientset()
365+
eventRecorder := record.NewFakeRecorder(3)
366+
367+
listener := NewBucketClaimListener()
368+
listener.InitializeKubeClient(kubeClient)
369+
listener.InitializeBucketClient(client)
370+
listener.InitializeEventRecorder(eventRecorder)
371+
372+
bucketclass, err := util.CreateBucketClass(ctx, client, &goldClass)
373+
if err != nil {
374+
t.Fatalf("Error occurred when creating BucketClass: %v", err)
375+
}
376+
377+
bucketClaim, err := util.CreateBucketClaim(ctx, client, &bucketClaim1)
378+
if err != nil {
379+
t.Fatalf("Error occurred when creating BucketClaim: %v", err)
380+
}
381+
382+
// Cleanup
383+
defer util.DeleteObjects(ctx, client, *bucketClaim, *bucketclass)
384+
385+
// Simulate concurrent modification by updating the BucketClaim in a goroutine
386+
// This will cause resourceVersion to change, simulating a conflict scenario
387+
var wg sync.WaitGroup
388+
wg.Add(1)
389+
go func() {
390+
defer wg.Done()
391+
for i := range 10 {
392+
// Fetch and update the BucketClaim to change its resourceVersion
393+
bc, getErr := client.ObjectstorageV1alpha1().BucketClaims(bucketClaim.Namespace).Get(
394+
ctx,
395+
bucketClaim.Name,
396+
metav1.GetOptions{},
397+
)
398+
if getErr != nil {
399+
return
400+
}
401+
// Add an annotation to change the resourceVersion
402+
if bc.Annotations == nil {
403+
bc.Annotations = make(map[string]string)
404+
}
405+
bc.Annotations[fmt.Sprintf("test-%d", i)] = "value"
406+
_, _ = client.ObjectstorageV1alpha1().BucketClaims(bc.Namespace).Update(
407+
ctx,
408+
bc,
409+
metav1.UpdateOptions{},
410+
)
411+
}
412+
}()
413+
414+
// Call Add which should handle conflicts with retry logic
415+
err = listener.Add(ctx, bucketClaim)
416+
if err != nil {
417+
t.Fatalf("Add should succeed even with concurrent modifications: %v", err)
418+
}
419+
420+
// Wait for the goroutine to complete to ensure all concurrent updates are done
421+
wg.Wait()
422+
423+
// Verify the final state - status should be updated correctly
424+
updatedClaim, err := client.ObjectstorageV1alpha1().BucketClaims(bucketClaim.Namespace).Get(
425+
ctx,
426+
bucketClaim.Name,
427+
metav1.GetOptions{},
428+
)
429+
if err != nil {
430+
t.Fatalf("Error occurred when reading BucketClaim: %v", err)
431+
}
432+
433+
// Verify status was updated
434+
expectedBucketName := fmt.Sprintf("bucket-%s", bucketClaim.UID)
435+
if updatedClaim.Status.BucketName != expectedBucketName {
436+
t.Errorf("Expected BucketName %s, got %s", expectedBucketName, updatedClaim.Status.BucketName)
437+
}
438+
439+
if updatedClaim.Status.BucketReady != false {
440+
t.Errorf("Expected BucketReady to be false, got %v", updatedClaim.Status.BucketReady)
441+
}
442+
443+
// Verify finalizer was added
444+
if !controllerutil.ContainsFinalizer(updatedClaim, util.BucketClaimFinalizer) {
445+
t.Errorf("Expected finalizer to be added, but it was not found")
446+
}
447+
}

vendor/k8s.io/client-go/util/retry/OWNERS

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/k8s.io/client-go/util/retry/util.go

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,7 @@ k8s.io/client-go/util/consistencydetector
756756
k8s.io/client-go/util/flowcontrol
757757
k8s.io/client-go/util/homedir
758758
k8s.io/client-go/util/keyutil
759+
k8s.io/client-go/util/retry
759760
k8s.io/client-go/util/watchlist
760761
k8s.io/client-go/util/workqueue
761762
# k8s.io/klog/v2 v2.130.1

0 commit comments

Comments
 (0)