Skip to content

Commit d429563

Browse files
committed
Retry project Delete attempts with backoff
1 parent b0852ea commit d429563

File tree

1 file changed

+47
-19
lines changed
  • pkg/project/apiserver/registry/project/proxy

1 file changed

+47
-19
lines changed

pkg/project/apiserver/registry/project/proxy/proxy.go

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package proxy
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
kerrors "k8s.io/apimachinery/pkg/api/errors"
89
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1011
"k8s.io/apimachinery/pkg/runtime"
12+
"k8s.io/apimachinery/pkg/util/wait"
1113
"k8s.io/apimachinery/pkg/watch"
1214
apirequest "k8s.io/apiserver/pkg/endpoints/request"
1315
"k8s.io/apiserver/pkg/registry/rest"
@@ -208,32 +210,58 @@ func (s *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje
208210

209211
var _ = rest.GracefulDeleter(&REST{})
210212

213+
// maxRetriesOnConflict is the maximum retry count for Delete calls which
214+
// result in resource conflicts.
215+
const maxRetriesOnConflict = 10
216+
217+
// maxDuration set max duration of delete retries. Deleting a project affects apiserver latency,
218+
// so this should be kept as small as possible
219+
const maxDuration = time.Second
220+
211221
// Delete deletes a Project specified by its name
212222
func (s *REST) Delete(ctx context.Context, name string, objectFunc rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
213223
var opts metav1.DeleteOptions
214224
if options != nil {
215225
opts = *options
216226
}
217-
if objectFunc != nil {
218-
obj, err := s.Get(ctx, name, &metav1.GetOptions{})
219-
if err != nil {
220-
return nil, false, err
221-
}
222-
projectObj, ok := obj.(*projectapi.Project)
223-
if !ok || projectObj == nil {
224-
return nil, false, fmt.Errorf("not a project: %#v", obj)
227+
var lastErr error
228+
err := wait.ExponentialBackoffWithContext(ctx, wait.Backoff{Steps: maxRetriesOnConflict, Duration: maxDuration}, func(ctx context.Context) (bool, error) {
229+
var err error
230+
if objectFunc != nil {
231+
var obj runtime.Object
232+
obj, err = s.Get(ctx, name, &metav1.GetOptions{})
233+
if err != nil {
234+
return false, fmt.Errorf("unable to get project: %w", err)
235+
}
236+
projectObj, ok := obj.(*projectapi.Project)
237+
if !ok || projectObj == nil {
238+
return false, fmt.Errorf("not a project: %#v", obj)
239+
}
240+
241+
// Make sure the object hasn't changed between Get and Delete - pass UID and RV to delete options
242+
if opts.Preconditions == nil {
243+
opts.Preconditions = &metav1.Preconditions{}
244+
}
245+
opts.Preconditions.UID = &projectObj.UID
246+
opts.Preconditions.ResourceVersion = &projectObj.ResourceVersion
247+
248+
if err := objectFunc(ctx, obj); err != nil {
249+
return false, fmt.Errorf("validation func failed: %w", err)
250+
}
225251
}
226-
227-
// Make sure the object hasn't changed between Get and Delete - pass UID and RV to delete options
228-
if opts.Preconditions == nil {
229-
opts.Preconditions = &metav1.Preconditions{}
230-
}
231-
opts.Preconditions.UID = &projectObj.UID
232-
opts.Preconditions.ResourceVersion = &projectObj.ResourceVersion
233-
234-
if err := objectFunc(ctx, obj); err != nil {
235-
return nil, false, err
252+
err = s.client.Delete(ctx, name, opts)
253+
switch {
254+
case err == nil:
255+
return true, nil
256+
case kerrors.IsConflict(err):
257+
lastErr = err
258+
return false, nil
259+
default:
260+
return false, err
236261
}
262+
})
263+
if wait.ErrorInterrupted(err) != nil {
264+
return &metav1.Status{Status: metav1.StatusFailure}, false, lastErr
237265
}
238-
return &metav1.Status{Status: metav1.StatusSuccess}, false, s.client.Delete(ctx, name, opts)
266+
return &metav1.Status{Status: metav1.StatusSuccess}, false, nil
239267
}

0 commit comments

Comments
 (0)