Skip to content

Commit 01f9a1e

Browse files
authored
Merge pull request #329 from llxp/fix/reconcile-delete
🐛 properly handle deletion of IPAddressClaims in ipamutil.ClaimReconciler
2 parents 8b168ef + 4d9d0a9 commit 01f9a1e

File tree

2 files changed

+39
-34
lines changed

2 files changed

+39
-34
lines changed

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ linters:
229229
- linters:
230230
- staticcheck
231231
text: 'QF1003: could use tagged switch on .*'
232+
- linters:
233+
- staticcheck
234+
text: 'QF1008: could remove embedded field'
232235
paths:
233236
- zz_generated.*\.go$
234237
- vendored_openapi\.go$

pkg/ipamutil/reconciler.go

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,16 @@ type ProviderAdapter interface {
6868
// ClaimHandler knows how to allocate and release IP addresses for a specific provider.
6969
type ClaimHandler interface {
7070
// FetchPool is called to fetch the pool referenced by the claim. The pool needs to be stored by the handler.
71+
// This method is called before EnsureAddress and ReleaseAddress.
72+
// Note that the ClaimReconciler will call the ReleaseAddress method regardless whether fetching the pool was
73+
// successful or not.
7174
FetchPool(ctx context.Context) (client.Object, *ctrl.Result, error)
7275
// EnsureAddress is called to make sure that the IPAddress.Spec is correct and the address is allocated.
76+
// It will only be called when FetchPool was successful.
7377
EnsureAddress(ctx context.Context, address *ipamv1.IPAddress) (*ctrl.Result, error)
7478
// ReleaseAddress is called to release the ip address that was allocated for the claim.
79+
// It will be called even if FetchPool was not successful when reconciling a deleted claim, so there is no guarantee
80+
// that the claim is available in the handler.
7581
ReleaseAddress(ctx context.Context) (*ctrl.Result, error)
7682
}
7783

@@ -138,24 +144,18 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
138144
cluster, err = clusterutil.GetClusterFromMetadata(ctx, r.Client, claim.ObjectMeta)
139145
}
140146
if err != nil {
141-
if apierrors.IsNotFound(err) {
142-
if !claim.DeletionTimestamp.IsZero() {
143-
patch := client.MergeFrom(claim.DeepCopy())
144-
if err := r.reconcileDelete(ctx, claim); err != nil {
145-
return ctrl.Result{}, fmt.Errorf("reconcile delete: %w", err)
146-
}
147-
// we'll need to explicitly patch the claim here since we haven't set up a patch helper yet.
148-
if err := r.Patch(ctx, claim, patch); err != nil {
149-
return ctrl.Result{}, fmt.Errorf("patch after reconciling delete: %w", err)
150-
}
151-
return ctrl.Result{}, nil
152-
}
147+
if !apierrors.IsNotFound(err) {
148+
log.Error(err, "error fetching cluster linked to IPAddressClaim")
149+
return ctrl.Result{}, err
150+
}
151+
if !claim.ObjectMeta.DeletionTimestamp.IsZero() {
152+
// In case the claim is deleted, we'll process it even when we can't find the cluster. During cluster deletion it
153+
// can happen that the Cluster object is released before all claims are cleaned up, which would block the claims indefinitely.
154+
log.Info("IPAddressClaim linked to a cluster that is not found, unable to determine cluster's paused state, proceeding with deletion")
155+
} else {
153156
log.Info("IPAddressClaim linked to a cluster that is not found, unable to determine cluster's paused state, skipping reconciliation")
154157
return ctrl.Result{}, nil
155158
}
156-
157-
log.Error(err, "error fetching cluster linked to IPAddressClaim")
158-
return ctrl.Result{}, err
159159
}
160160
if cluster != nil {
161161
if annotations.IsPaused(cluster, cluster) {
@@ -176,7 +176,9 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
176176
}
177177
}()
178178

179-
controllerutil.AddFinalizer(claim, ReleaseAddressFinalizer)
179+
if controllerutil.AddFinalizer(claim, ReleaseAddressFinalizer) {
180+
return ctrl.Result{}, nil
181+
}
180182

181183
var res *reconcile.Result
182184
var pool client.Object
@@ -185,10 +187,10 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
185187
handler := r.Adapter.ClaimHandlerFor(r.Client, claim)
186188
if pool, res, err = handler.FetchPool(ctx); err != nil || res != nil {
187189
if apierrors.IsNotFound(err) {
188-
err := errors.New("pool not found")
190+
err := fmt.Errorf("pool not found: %w", err)
189191
log.Error(err, "the referenced pool could not be found")
190-
if !claim.DeletionTimestamp.IsZero() {
191-
return ctrl.Result{}, r.reconcileDelete(ctx, claim)
192+
if !claim.ObjectMeta.DeletionTimestamp.IsZero() {
193+
return r.reconcileDelete(ctx, claim, handler)
192194
}
193195
return ctrl.Result{}, nil
194196
}
@@ -206,12 +208,8 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
206208
return ctrl.Result{}, nil
207209
}
208210

209-
// If the claim is marked for deletion, release the address.
210-
if !claim.DeletionTimestamp.IsZero() {
211-
if res, err := handler.ReleaseAddress(ctx); err != nil {
212-
return unwrapResult(res), err
213-
}
214-
return ctrl.Result{}, r.reconcileDelete(ctx, claim)
211+
if !claim.ObjectMeta.DeletionTimestamp.IsZero() {
212+
return r.reconcileDelete(ctx, claim, handler)
215213
}
216214

217215
// We always ensure there is a valid address object passed to the handler.
@@ -261,34 +259,38 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
261259
return ctrl.Result{}, nil
262260
}
263261

264-
func (r *ClaimReconciler) reconcileDelete(ctx context.Context, claim *ipamv1.IPAddressClaim) error {
262+
func (r *ClaimReconciler) reconcileDelete(ctx context.Context, claim *ipamv1.IPAddressClaim, handler ClaimHandler) (ctrl.Result, error) {
263+
if res, err := handler.ReleaseAddress(ctx); err != nil {
264+
return unwrapResult(res), fmt.Errorf("release address: %w", err)
265+
}
266+
265267
address := &ipamv1.IPAddress{}
266268
namespacedName := types.NamespacedName{
267269
Namespace: claim.Namespace,
268270
Name: claim.Name,
269271
}
270-
if err := r.Get(ctx, namespacedName, address); err != nil && !apierrors.IsNotFound(err) {
271-
return errors.Wrap(err, "failed to fetch address")
272+
if err := r.Client.Get(ctx, namespacedName, address); err != nil && !apierrors.IsNotFound(err) {
273+
return ctrl.Result{}, errors.Wrap(err, "failed to fetch address")
272274
}
273275

274276
if address.Name != "" {
275277
var err error
276-
patch := client.MergeFrom(address.DeepCopy())
278+
p := client.MergeFrom(address.DeepCopy())
277279
if controllerutil.RemoveFinalizer(address, ProtectAddressFinalizer) {
278-
if err = r.Patch(ctx, address, patch); err != nil && !apierrors.IsNotFound(err) {
279-
return errors.Wrap(err, "failed to remove address finalizer")
280+
if err = r.Client.Patch(ctx, address, p); err != nil && !apierrors.IsNotFound(err) {
281+
return ctrl.Result{}, errors.Wrap(err, "failed to remove address finalizer")
280282
}
281283
}
282284

283285
if err == nil {
284-
if err := r.Delete(ctx, address); err != nil && !apierrors.IsNotFound(err) {
285-
return err
286+
if err := r.Client.Delete(ctx, address); err != nil && !apierrors.IsNotFound(err) {
287+
return ctrl.Result{}, err
286288
}
287289
}
288290
}
289291

290292
controllerutil.RemoveFinalizer(claim, ReleaseAddressFinalizer)
291-
return nil
293+
return ctrl.Result{}, nil
292294
}
293295

294296
func (r *ClaimReconciler) clusterToIPClaims(_ context.Context, a client.Object) []reconcile.Request {

0 commit comments

Comments
 (0)