Skip to content

Commit d679ab1

Browse files
committed
fixup storage tracking
1 parent 9be78b8 commit d679ab1

7 files changed

Lines changed: 218 additions & 42 deletions

File tree

apis/bases/rabbitmq.openstack.org_rabbitmqs.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,14 @@ spec:
19691969
the opentack-operator in the top-level CR (e.g. the ContainerImage)
19701970
format: int64
19711971
type: integer
1972+
pvsBeingDeleted:
1973+
description: |-
1974+
PVsBeingDeleted - list of PV names that are expected to be deleted during storage wipe
1975+
Tracked to avoid scanning all PVs in the cluster during cleanup verification
1976+
items:
1977+
type: string
1978+
type: array
1979+
x-kubernetes-list-type: set
19721980
queueType:
19731981
description: QueueType - store whether default ha-all policy is present
19741982
or not

apis/rabbitmq/v1beta1/rabbitmq_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ type RabbitMqStatus struct {
151151
// StorageWipeStartedAt - timestamp when storage wipe process started
152152
// Used to implement timeout protection against stuck PV/PVC deletions
153153
StorageWipeStartedAt *metav1.Time `json:"storageWipeStartedAt,omitempty"`
154+
155+
// PVsBeingDeleted - list of PV names that are expected to be deleted during storage wipe
156+
// Tracked to avoid scanning all PVs in the cluster during cleanup verification
157+
// +listType=set
158+
PVsBeingDeleted []string `json:"pvsBeingDeleted,omitempty"`
154159
}
155160

156161
//+kubebuilder:object:root=true

apis/rabbitmq/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/rabbitmq.openstack.org_rabbitmqs.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,14 @@ spec:
19691969
the opentack-operator in the top-level CR (e.g. the ContainerImage)
19701970
format: int64
19711971
type: integer
1972+
pvsBeingDeleted:
1973+
description: |-
1974+
PVsBeingDeleted - list of PV names that are expected to be deleted during storage wipe
1975+
Tracked to avoid scanning all PVs in the cluster during cleanup verification
1976+
items:
1977+
type: string
1978+
type: array
1979+
x-kubernetes-list-type: set
19721980
queueType:
19731981
description: QueueType - store whether default ha-all policy is present
19741982
or not

internal/controller/rabbitmq/rabbitmq_controller.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
598598
CurrentQueueType: string(instance.Status.QueueType),
599599
Reason: wipeReason,
600600
StorageWipeStartedAt: storageWipeStartedAt,
601+
PVsBeingDeleted: instance.Status.PVsBeingDeleted,
601602
DeleteCluster: func(ctx context.Context) error {
602603
err := rmqCluster.Delete(ctx, helper)
603604
if err != nil && !k8s_errors.IsNotFound(err) {
@@ -618,6 +619,17 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
618619
return result, err
619620
}
620621

622+
// Update status with PV tracking information (if changed during this reconcile)
623+
if len(wipeParams.PVsBeingDeleted) > 0 && len(instance.Status.PVsBeingDeleted) == 0 {
624+
instance.Status.PVsBeingDeleted = wipeParams.PVsBeingDeleted
625+
// Persist status immediately so PV tracking survives across reconciles
626+
if err := helper.PatchInstance(ctx, instance); err != nil {
627+
Log.Error(err, "Failed to update status with PV tracking")
628+
return ctrl.Result{}, err
629+
}
630+
Log.Info("Updated status with PV tracking", "pvCount", len(wipeParams.PVsBeingDeleted))
631+
}
632+
621633
// If result has Requeue set, we're still in progress
622634
if result.Requeue || result.RequeueAfter > 0 {
623635
return result, nil
@@ -628,9 +640,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
628640
if instance.Annotations != nil {
629641
if targetVersion, hasTarget := instance.Annotations[rabbitmqv1beta1.AnnotationTargetVersion]; hasTarget && targetVersion != "" {
630642
instance.Status.CurrentVersion = targetVersion
631-
// Clear the upgrade phase and timestamp
643+
// Clear the upgrade phase, timestamp, and PV tracking
632644
instance.Status.UpgradePhase = ""
633645
instance.Status.StorageWipeStartedAt = nil
646+
instance.Status.PVsBeingDeleted = nil
634647

635648
// If queue type changed during upgrade, update Status.QueueType to prevent
636649
// triggering another wipe for "queue type migration"
@@ -654,6 +667,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
654667
// Queue migration complete - update Status.QueueType
655668
instance.Status.UpgradePhase = ""
656669
instance.Status.StorageWipeStartedAt = nil
670+
instance.Status.PVsBeingDeleted = nil
657671
if instance.Spec.QueueType != nil {
658672
switch *instance.Spec.QueueType {
659673
case rabbitmqv1beta1.QueueTypeQuorum:

internal/controller/rabbitmq/storage_upgrade.go

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)