containerized-data-importer/pkg/controller/transfer/pvc.go
alromeros 4c911fed27
Fix PVC deletion logic in populators (#3362)
* Fix PVC' deletion logic in CDI populators

This commit ensures that we only proceed with PVC' deletion after confirming that the target PVC is rebound.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Refactor IsBound so we check PVC status instead of spec

PVC status should be the definitive source to know whether the PVC is bound or not. This commit updates our IsBound function to use status instead of spec.

It also updates unit tests and related code to match the new behavior.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Add check for lost target PVC in populators

Signed-off-by: Alvaro Romero <alromero@redhat.com>

---------

Signed-off-by: Alvaro Romero <alromero@redhat.com>
2024-08-22 22:22:35 +02:00

219 lines
6.5 KiB
Go

package transfer
import (
"context"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
)
const (
annBindCompleted = "pv.kubernetes.io/bind-completed"
pvcCloneFinalizer = "provisioner.storage.kubernetes.io/cloning-protection"
pvcSnapshotFinalizer = "snapshot.storage.kubernetes.io/pvc-as-source-protection"
)
type pvcTransferHandler struct {
objectTransferHandler
}
func (h *pvcTransferHandler) ReconcilePending(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
pvc := &corev1.PersistentVolumeClaim{}
pvcExists, err := h.reconciler.getSourceResource(ot, pvc)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if !pvcExists {
// will reconcile again when pvc is created/updated
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "No source", ""); err != nil {
return 0, err
}
return 0, nil
}
if cc.IsUnbound(pvc) {
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PVC not bound", ""); err != nil {
return 0, err
}
return 0, nil
}
for _, f := range []string{pvcCloneFinalizer, pvcSnapshotFinalizer} {
if cc.HasFinalizer(pvc, f) {
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PVC has finalizer: "+f, ""); err != nil {
return 0, err
}
return 0, nil
}
}
pv := &corev1.PersistentVolume{}
if err := h.reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: pvc.Spec.VolumeName}, pv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if pv.Spec.ClaimRef == nil ||
pv.Spec.ClaimRef.Namespace != pvc.Namespace ||
pv.Spec.ClaimRef.Name != pvc.Name {
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PV not bound", ""); err != nil {
return 0, err
}
return 0, nil
}
pods, err := cc.GetPodsUsingPVCs(context.TODO(), h.reconciler.Client, pvc.Namespace, sets.New(pvc.Name), false)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if len(pods) > 0 {
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Pods using PVC", ""); err != nil {
return 0, err
}
return defaultRequeue, nil
}
pvc2 := pvc.DeepCopy()
pvc2.Status = corev1.PersistentVolumeClaimStatus{}
data := map[string]string{
"pvName": pv.Name,
}
return 0, h.reconciler.pendingHelper(ot, pvc2, data)
}
func (h *pvcTransferHandler) ReconcileRunning(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
pvName, ok := ot.Status.Data["pvName"]
if !ok {
ot.Status.Phase = cdiv1.ObjectTransferError
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PV name missing", ""); err != nil {
return 0, err
}
return 0, nil
}
pv := &corev1.PersistentVolume{}
if err := h.reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: pvName}, pv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
reclaim, ok := ot.Status.Data["pvReclaim"]
if !ok {
ot.Status.Data["pvReclaim"] = string(pv.Spec.PersistentVolumeReclaimPolicy)
if err := h.reconciler.setCompleteConditionRunning(ot); err != nil {
return 0, err
}
return 0, nil
}
source := &corev1.PersistentVolumeClaim{}
sourceExists, err := h.reconciler.getSourceResource(ot, source)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
// must assert pointing to same PV to avoid races/contention with smartclone controller
if sourceExists && source.Spec.VolumeName == pv.Name {
if pv.Spec.PersistentVolumeReclaimPolicy != corev1.PersistentVolumeReclaimRetain {
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
if err := h.reconciler.updateResource(ot, pv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
return 0, h.reconciler.setCompleteConditionRunning(ot)
}
if source.DeletionTimestamp == nil {
if err := h.reconciler.Client.Delete(context.TODO(), source); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
}
return 0, h.reconciler.setCompleteConditionRunning(ot)
}
if pv.Spec.ClaimRef == nil ||
(pv.Spec.ClaimRef.Namespace == ot.Spec.Source.Namespace && pv.Spec.ClaimRef.Name == ot.Spec.Source.Name) {
pv.Spec.ClaimRef = &corev1.ObjectReference{
Namespace: getTransferTargetNamespace(ot),
Name: getTransferTargetName(ot),
}
if err := h.reconciler.updateResource(ot, pv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
}
if pv.Spec.ClaimRef.Namespace != getTransferTargetNamespace(ot) ||
pv.Spec.ClaimRef.Name != getTransferTargetName(ot) {
ot.Status.Phase = cdiv1.ObjectTransferError
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PV bound to wrong PVC", ""); err != nil {
return 0, err
}
// TODO what to do here
return 0, fmt.Errorf("PV bound to wrong PVC")
}
target := &corev1.PersistentVolumeClaim{}
targetExists, err := h.reconciler.getTargetResource(ot, target)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if !targetExists {
target = &corev1.PersistentVolumeClaim{}
if err := h.reconciler.createObjectTransferTarget(ot, target, func(o client.Object) {
delete(o.GetAnnotations(), annBindCompleted)
}); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
return 0, h.reconciler.setCompleteConditionRunning(ot)
}
if target.Status.Phase != corev1.ClaimBound {
ot.Status.Phase = cdiv1.ObjectTransferRunning
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Waiting for target to be bound", ""); err != nil {
return 0, err
}
return 0, nil
}
if pv.Spec.PersistentVolumeReclaimPolicy != corev1.PersistentVolumeReclaimPolicy(reclaim) {
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimPolicy(reclaim)
if err := h.reconciler.updateResource(ot, pv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
return 0, h.reconciler.setCompleteConditionRunning(ot)
}
ot.Status.Phase = cdiv1.ObjectTransferComplete
ot.Status.Data = nil
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionTrue, "Transfer complete", ""); err != nil {
return 0, err
}
return 0, nil
}