@@ -68,10 +68,16 @@ type ProviderAdapter interface {
68
68
// ClaimHandler knows how to allocate and release IP addresses for a specific provider.
69
69
type ClaimHandler interface {
70
70
// 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.
71
74
FetchPool (ctx context.Context ) (client.Object , * ctrl.Result , error )
72
75
// 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.
73
77
EnsureAddress (ctx context.Context , address * ipamv1.IPAddress ) (* ctrl.Result , error )
74
78
// 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.
75
81
ReleaseAddress (ctx context.Context ) (* ctrl.Result , error )
76
82
}
77
83
@@ -138,24 +144,18 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
138
144
cluster , err = clusterutil .GetClusterFromMetadata (ctx , r .Client , claim .ObjectMeta )
139
145
}
140
146
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 {
153
156
log .Info ("IPAddressClaim linked to a cluster that is not found, unable to determine cluster's paused state, skipping reconciliation" )
154
157
return ctrl.Result {}, nil
155
158
}
156
-
157
- log .Error (err , "error fetching cluster linked to IPAddressClaim" )
158
- return ctrl.Result {}, err
159
159
}
160
160
if cluster != nil {
161
161
if annotations .IsPaused (cluster , cluster ) {
@@ -176,7 +176,9 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
176
176
}
177
177
}()
178
178
179
- controllerutil .AddFinalizer (claim , ReleaseAddressFinalizer )
179
+ if controllerutil .AddFinalizer (claim , ReleaseAddressFinalizer ) {
180
+ return ctrl.Result {}, nil
181
+ }
180
182
181
183
var res * reconcile.Result
182
184
var pool client.Object
@@ -185,10 +187,10 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
185
187
handler := r .Adapter .ClaimHandlerFor (r .Client , claim )
186
188
if pool , res , err = handler .FetchPool (ctx ); err != nil || res != nil {
187
189
if apierrors .IsNotFound (err ) {
188
- err := errors . New ("pool not found" )
190
+ err := fmt . Errorf ("pool not found: %w" , err )
189
191
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 )
192
194
}
193
195
return ctrl.Result {}, nil
194
196
}
@@ -206,12 +208,8 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
206
208
return ctrl.Result {}, nil
207
209
}
208
210
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 )
215
213
}
216
214
217
215
// 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
261
259
return ctrl.Result {}, nil
262
260
}
263
261
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
+
265
267
address := & ipamv1.IPAddress {}
266
268
namespacedName := types.NamespacedName {
267
269
Namespace : claim .Namespace ,
268
270
Name : claim .Name ,
269
271
}
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" )
272
274
}
273
275
274
276
if address .Name != "" {
275
277
var err error
276
- patch := client .MergeFrom (address .DeepCopy ())
278
+ p := client .MergeFrom (address .DeepCopy ())
277
279
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" )
280
282
}
281
283
}
282
284
283
285
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
286
288
}
287
289
}
288
290
}
289
291
290
292
controllerutil .RemoveFinalizer (claim , ReleaseAddressFinalizer )
291
- return nil
293
+ return ctrl. Result {}, nil
292
294
}
293
295
294
296
func (r * ClaimReconciler ) clusterToIPClaims (_ context.Context , a client.Object ) []reconcile.Request {
0 commit comments