Skip to content

Commit 31544ee

Browse files
authored
Do not retry updates on conflict (#217)
* Do not retry on conflict When the reconciliation loops tries to update a CR, and the result is a Conflict, the reconciliation loop should not retry because this error will never succeed. The reconciliation loop needs to be restarted. Additionally, there was a situation where the resource would be deleted, but the Updater would still try to update the deleted resource, resulting in errors.
1 parent 8c7e1e2 commit 31544ee

File tree

2 files changed

+57
-27
lines changed

2 files changed

+57
-27
lines changed

pkg/reconciler/internal/updater/updater.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import (
2121

2222
"helm.sh/helm/v3/pkg/release"
2323
corev1 "k8s.io/api/core/v1"
24+
"k8s.io/apimachinery/pkg/api/errors"
2425
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2526
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/util/wait"
2628
"k8s.io/client-go/util/retry"
2729
"sigs.k8s.io/controller-runtime/pkg/client"
2830

@@ -37,6 +39,7 @@ func New(client client.Client) Updater {
3739
}
3840

3941
type Updater struct {
42+
isCanceled bool
4043
client client.Client
4144
updateFuncs []UpdateFunc
4245
updateStatusFuncs []UpdateStatusFunc
@@ -53,13 +56,38 @@ func (u *Updater) UpdateStatus(fs ...UpdateStatusFunc) {
5356
u.updateStatusFuncs = append(u.updateStatusFuncs, fs...)
5457
}
5558

59+
func (u *Updater) CancelUpdates() {
60+
u.isCanceled = true
61+
}
62+
63+
func isRetryableUpdateError(err error) bool {
64+
return !errors.IsConflict(err) && !errors.IsNotFound(err)
65+
}
66+
67+
// retryOnRetryableUpdateError retries the given function until it succeeds,
68+
// until the given backoff is exhausted, or until the error is not retryable.
69+
//
70+
// In case of a Conflict error, the update cannot be retried because the underlying
71+
// resource has been modified in the meantime, and the reconciliation loop needs
72+
// to be restarted anew.
73+
//
74+
// A NotFound error means that the object has been deleted, and the reconciliation loop
75+
// needs to be restarted anew as well.
76+
func retryOnRetryableUpdateError(backoff wait.Backoff, f func() error) error {
77+
return retry.OnError(backoff, isRetryableUpdateError, f)
78+
}
79+
5680
func (u *Updater) Apply(ctx context.Context, obj *unstructured.Unstructured) error {
81+
if u.isCanceled {
82+
return nil
83+
}
84+
5785
backoff := retry.DefaultRetry
5886

5987
// Always update the status first. During uninstall, if
6088
// we remove the finalizer, updating the status will fail
61-
// because the object and its status will be garbage-collected
62-
if err := retry.RetryOnConflict(backoff, func() error {
89+
// because the object and its status will be garbage-collected.
90+
if err := retryOnRetryableUpdateError(backoff, func() error {
6391
st := statusFor(obj)
6492
needsStatusUpdate := false
6593
for _, f := range u.updateStatusFuncs {
@@ -78,7 +106,7 @@ func (u *Updater) Apply(ctx context.Context, obj *unstructured.Unstructured) err
78106
return err
79107
}
80108

81-
if err := retry.RetryOnConflict(backoff, func() error {
109+
if err := retryOnRetryableUpdateError(backoff, func() error {
82110
needsUpdate := false
83111
for _, f := range u.updateFuncs {
84112
needsUpdate = f(obj) || needsUpdate

pkg/reconciler/reconciler.go

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.
567567
u.UpdateStatus(updater.EnsureCondition(conditions.Initialized(corev1.ConditionTrue, "", "")))
568568

569569
if obj.GetDeletionTimestamp() != nil {
570-
err := r.handleDeletion(ctx, actionClient, obj, log)
571-
return ctrl.Result{}, err
570+
if err := r.handleDeletion(ctx, actionClient, obj, log); err != nil {
571+
return ctrl.Result{}, err
572+
}
573+
u.CancelUpdates()
574+
return ctrl.Result{}, nil
572575
}
573576

574577
vals, err := r.getValues(ctx, obj)
@@ -660,28 +663,27 @@ const (
660663
)
661664

662665
func (r *Reconciler) handleDeletion(ctx context.Context, actionClient helmclient.ActionInterface, obj *unstructured.Unstructured, log logr.Logger) error {
663-
if !controllerutil.ContainsFinalizer(obj, uninstallFinalizer) {
664-
log.Info("Resource is terminated, skipping reconciliation")
665-
return nil
666-
}
667-
668-
// Use defer in a closure so that it executes before we wait for
669-
// the deletion of the CR. This might seem unnecessary since we're
670-
// applying changes to the CR after is has a deletion timestamp.
671-
// However, if uninstall fails, the finalizer will not be removed
672-
// and we need to be able to update the conditions on the CR to
673-
// indicate that the uninstall failed.
674-
if err := func() (err error) {
675-
uninstallUpdater := updater.New(r.client)
676-
defer func() {
677-
applyErr := uninstallUpdater.Apply(ctx, obj)
678-
if err == nil {
679-
err = applyErr
680-
}
681-
}()
682-
return r.doUninstall(actionClient, &uninstallUpdater, obj, log)
683-
}(); err != nil {
684-
return err
666+
if controllerutil.ContainsFinalizer(obj, uninstallFinalizer) {
667+
// Use defer in a closure so that it executes before we wait for
668+
// the deletion of the CR. This might seem unnecessary since we're
669+
// applying changes to the CR after is has a deletion timestamp.
670+
// However, if uninstall fails, the finalizer will not be removed
671+
// and we need to be able to update the conditions on the CR to
672+
// indicate that the uninstall failed.
673+
if err := func() (err error) {
674+
uninstallUpdater := updater.New(r.client)
675+
defer func() {
676+
applyErr := uninstallUpdater.Apply(ctx, obj)
677+
if err == nil {
678+
err = applyErr
679+
}
680+
}()
681+
return r.doUninstall(actionClient, &uninstallUpdater, obj, log)
682+
}(); err != nil {
683+
return err
684+
}
685+
} else {
686+
log.Info("Resource is already terminated, skipping deletion.")
685687
}
686688

687689
// Since the client is hitting a cache, waiting for the

0 commit comments

Comments
 (0)