@@ -75,7 +75,6 @@ import (
7575 "context"
7676 "fmt"
7777 "sort"
78- "strings"
7978 "time"
8079
8180 "github.com/go-logr/logr"
@@ -184,33 +183,77 @@ func removeProtectionFinalizer(pvc *corev1.PersistentVolumeClaim, log logr.Logge
184183 return false
185184}
186185
187- // verifyPVCleanupComplete checks if all PVs associated with the RabbitMQ instance
188- // have been deleted. Returns true if cleanup is complete, false if still in progress.
186+ // verifyPVCleanupComplete checks if all PVs from the provided list have been deleted.
187+ // Returns true if cleanup is complete, false if still in progress.
189188//
190189// Verifies clean storage before recreating the cluster.
191190//
191+ // For PVs in Released state (PVC deleted but PV still exists), this function
192+ // clears the ClaimRef to trigger PV deletion per the reclaim policy.
193+ // This is necessary for static PVs (like local-storage) that may get stuck
194+ // in Released state even with reclaimPolicy: Delete.
195+ //
192196// Implements timeout protection (30 minutes) for PV deletion.
193- func (r * Reconciler ) verifyPVCleanupComplete (ctx context.Context , namespace , instanceName string , storageWipeStartedAt * time.Time , log logr.Logger ) (bool , error ) {
194- pvList := & corev1.PersistentVolumeList {}
195- if err := r .List (ctx , pvList ); err != nil {
196- return false , fmt .Errorf ("failed to list PVs during upgrade verification: %w. " +
197- "List PVs: kubectl get pv -A" , err )
197+ //
198+ // pvNames is the list of PV names that were captured during PVC deletion.
199+ // This optimization avoids scanning all PVs in the cluster.
200+ func (r * Reconciler ) verifyPVCleanupComplete (ctx context.Context , pvNames []string , storageWipeStartedAt * time.Time , log logr.Logger ) (bool , error ) {
201+ // If no PVs were tracked, cleanup is complete
202+ if len (pvNames ) == 0 {
203+ log .Info ("No PVs to clean up" )
204+ return true , nil
198205 }
199206
200- // Check if any PVs are still referencing our PVCs
201- pvcPrefix := fmt .Sprintf ("persistence-%s-" , instanceName )
207+ // Check which PVs still exist and remove protection finalizers if needed
202208 var stuckPVs []string
203- for _ , pv := range pvList .Items {
204- if pv .Spec .ClaimRef != nil &&
205- pv .Spec .ClaimRef .Namespace == namespace &&
206- strings .HasPrefix (pv .Spec .ClaimRef .Name , pvcPrefix ) {
207-
208- log .Info ("Waiting for PV cleanup to complete" ,
209- "pvName" , pv .Name ,
210- "phase" , pv .Status .Phase ,
211- "pvcName" , pv .Spec .ClaimRef .Name )
212- stuckPVs = append (stuckPVs , pv .Name )
209+ for _ , pvName := range pvNames {
210+ pv := & corev1.PersistentVolume {}
211+ err := r .Get (ctx , types.NamespacedName {Name : pvName }, pv )
212+
213+ // If PV doesn't exist, it's been cleaned up (expected)
214+ if k8s_errors .IsNotFound (err ) {
215+ continue
216+ }
217+
218+ // If we can't get the PV for other reasons, include it to be safe
219+ if err != nil {
220+ log .V (1 ).Info ("Unable to get PV, including in cleanup check" ,
221+ "pvName" , pvName ,
222+ "error" , err )
223+ stuckPVs = append (stuckPVs , pvName )
224+ continue
225+ }
226+
227+ // If PV is in Released phase (PVC deleted but PV still exists),
228+ // clear the ClaimRef to trigger deletion
229+ if pv .Status .Phase == corev1 .VolumeReleased && pv .Spec .ClaimRef != nil {
230+ log .Info ("Clearing ClaimRef from Released PV to trigger deletion" ,
231+ "pvName" , pvName ,
232+ "claimRef" , pv .Spec .ClaimRef .Name ,
233+ "reclaimPolicy" , pv .Spec .PersistentVolumeReclaimPolicy )
234+
235+ pv .Spec .ClaimRef = nil
236+
237+ if err := r .Update (ctx , pv ); err != nil {
238+ log .Error (err , "Failed to clear ClaimRef from Released PV" ,
239+ "pvName" , pvName )
240+ // Don't fail - just log and continue, will retry next reconcile
241+ } else {
242+ log .Info ("Cleared ClaimRef - PV will now be deleted by reclaim policy" ,
243+ "pvName" , pvName )
244+ }
213245 }
246+
247+ // PV still exists - track it
248+ pvcName := ""
249+ if pv .Spec .ClaimRef != nil {
250+ pvcName = pv .Spec .ClaimRef .Name
251+ }
252+ log .Info ("Waiting for PV cleanup to complete" ,
253+ "pvName" , pvName ,
254+ "phase" , pv .Status .Phase ,
255+ "pvcName" , pvcName )
256+ stuckPVs = append (stuckPVs , pvName )
214257 }
215258
216259 if len (stuckPVs ) > 0 {
@@ -250,10 +293,10 @@ func (r *Reconciler) verifyPVCleanupComplete(ctx context.Context, namespace, ins
250293// 1. PV deletion (due to Delete reclaim policy)
251294// 2. Storage backend cleanup
252295//
253- // Returns true if PVCs were deleted.
254- func (r * Reconciler ) deletePVCsForUpgrade (ctx context.Context , pvcList * corev1.PersistentVolumeClaimList , log logr.Logger ) (bool , error ) {
296+ // Returns true if PVCs were deleted and the list of PV names that should be deleted .
297+ func (r * Reconciler ) deletePVCsForUpgrade (ctx context.Context , pvcList * corev1.PersistentVolumeClaimList , log logr.Logger ) (bool , [] string , error ) {
255298 if len (pvcList .Items ) == 0 {
256- return false , nil
299+ return false , nil , nil
257300 }
258301
259302 // Sort PVCs by name to ensure deterministic deletion order
@@ -264,14 +307,17 @@ func (r *Reconciler) deletePVCsForUpgrade(ctx context.Context, pvcList *corev1.P
264307
265308 log .Info ("Deleting PVCs in deterministic order" , "pvcCount" , len (pvcList .Items ))
266309
310+ // Track which PVs should be deleted (for efficient cleanup verification)
311+ pvNames := make ([]string , 0 , len (pvcList .Items ))
312+
267313 for i := range pvcList .Items {
268314 pvc := & pvcList .Items [i ]
269315
270316 // Remove kubernetes.io/pvc-protection finalizer to allow deletion
271317 // Other finalizers are preserved for CSI driver cleanup
272318 if removeProtectionFinalizer (pvc , log ) {
273319 if err := r .Update (ctx , pvc ); err != nil {
274- return false , fmt .Errorf ("failed to remove finalizer from PVC %s: %w. " +
320+ return false , nil , fmt .Errorf ("failed to remove finalizer from PVC %s: %w. " +
275321 "Check PVC: kubectl get pvc %s -n %s -o yaml" ,
276322 pvc .Name , err , pvc .Name , pvc .Namespace )
277323 }
@@ -280,17 +326,22 @@ func (r *Reconciler) deletePVCsForUpgrade(ctx context.Context, pvcList *corev1.P
280326 // Delete the PVC
281327 // The Delete reclaim policy on the PV triggers automatic storage cleanup
282328 if err := r .Delete (ctx , pvc ); err != nil && ! k8s_errors .IsNotFound (err ) {
283- return false , fmt .Errorf ("failed to delete PVC %s: %w. " +
329+ return false , nil , fmt .Errorf ("failed to delete PVC %s: %w. " +
284330 "Check PVC: kubectl describe pvc %s -n %s" ,
285331 pvc .Name , err , pvc .Name , pvc .Namespace )
286332 }
287333
288334 log .Info ("Deleted PVC - storage will be automatically wiped by Delete reclaim policy" ,
289335 "pvcName" , pvc .Name ,
290336 "pvName" , pvc .Spec .VolumeName )
337+
338+ // Track the PV for cleanup verification
339+ if pvc .Spec .VolumeName != "" {
340+ pvNames = append (pvNames , pvc .Spec .VolumeName )
341+ }
291342 }
292343
293- return true , nil
344+ return true , pvNames , nil
294345}
295346
296347// waitForPodsTermination checks if all pods associated with the RabbitMQ instance
@@ -340,6 +391,8 @@ type StorageWipeParams struct {
340391 Reason string
341392 // Timestamp when storage wipe started (for timeout tracking)
342393 StorageWipeStartedAt * time.Time
394+ // PV names being deleted (populated during PVC deletion for efficient verification)
395+ PVsBeingDeleted []string
343396 // Function to delete the RabbitMQCluster
344397 DeleteCluster func (ctx context.Context ) error
345398 // Function to delete the ha-all mirrored policy (optional)
@@ -429,20 +482,24 @@ func (r *Reconciler) performStorageWipe(
429482
430483 // Step 4b: Delete all PVCs
431484 // The Delete reclaim policy will trigger automatic storage cleanup
432- pvcDeleted , err := r .deletePVCsForUpgrade (ctx , pvcList , log )
485+ pvcDeleted , pvNames , err := r .deletePVCsForUpgrade (ctx , pvcList , log )
433486 if err != nil {
434487 log .Error (err , "Failed to delete PVCs during storage wipe" )
435488 return ctrl.Result {}, err
436489 }
437490
438491 if pvcDeleted {
439- log .Info ("Deleted all PVCs, waiting for PV cleanup to complete" , "pvcCount" , len (pvcList .Items ))
492+ // Store the PV names for efficient cleanup verification
493+ params .PVsBeingDeleted = pvNames
494+ log .Info ("Deleted all PVCs, waiting for PV cleanup to complete" ,
495+ "pvcCount" , len (pvcList .Items ),
496+ "pvCount" , len (pvNames ))
440497 return ctrl.Result {RequeueAfter : UpgradeCheckInterval }, nil
441498 }
442499 }
443500
444501 // Step 5: Verify all PVs are completely cleaned up
445- cleanupComplete , err := r .verifyPVCleanupComplete (ctx , params .Namespace , params . InstanceName , params .StorageWipeStartedAt , log )
502+ cleanupComplete , err := r .verifyPVCleanupComplete (ctx , params .PVsBeingDeleted , params .StorageWipeStartedAt , log )
446503 if err != nil {
447504 log .Error (err , "Failed to verify PV cleanup" )
448505 return ctrl.Result {}, fmt .Errorf ("PV cleanup verification failed: %w. " +
0 commit comments