@@ -68,10 +68,16 @@ type ProviderAdapter interface {
6868// ClaimHandler knows how to allocate and release IP addresses for a specific provider.
6969type 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
294296func (r * ClaimReconciler ) clusterToIPClaims (_ context.Context , a client.Object ) []reconcile.Request {
0 commit comments