mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00
218 lines
6.5 KiB
Go
218 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"
|
|
cdicontroller "kubevirt.io/containerized-data-importer/pkg/controller"
|
|
)
|
|
|
|
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 pvc.Spec.VolumeName == "" || pvc.Status.Phase != corev1.ClaimBound {
|
|
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 cdicontroller.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 := cdicontroller.GetPodsUsingPVCs(h.reconciler.Client, pvc.Namespace, sets.NewString(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
|
|
}
|