containerized-data-importer/pkg/controller/transfer/dv.go
Michael Henriksen f88fab69dc
PVC Clone Populator (#2709)
* touch up zero restoresize snapshot

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* clone populator

only supports PVC source now

snapshot coming soon

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* more unit tests

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* unit test for clone populator

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* func tests for clone populator

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* move clone populator cleanup function to planner

other review comments

verifier pod should bount readonly

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* add readonly flag to test executor pods

synchronize get hash calls

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* increase linter timeout

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* better/explicit readonly support for test pods

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* check pv for driver info before looking up storageclass as it may not exist

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* addressed review comments

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* chooseStrategy shoud generate more events

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

---------

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>
2023-05-24 05:11:52 +02:00

269 lines
7.1 KiB
Go

package transfer
import (
"context"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
"kubevirt.io/containerized-data-importer/pkg/common"
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
"kubevirt.io/containerized-data-importer/pkg/util"
)
type dataVolumeTransferHandler struct {
objectTransferHandler
}
func (h *dataVolumeTransferHandler) ReconcilePending(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
dv := &cdiv1.DataVolume{}
dvExists, err := h.reconciler.getSourceResource(ot, dv)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if !dvExists {
// will reconcile again when dv is created/updated
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "No source", ""); err != nil {
return 0, err
}
return 0, nil
}
if dv.Status.Phase != cdiv1.Succeeded {
// will reconcile again when dv is updated
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Source not populated", ""); err != nil {
return 0, err
}
return 0, nil
}
pods, err := cc.GetPodsUsingPVCs(context.TODO(), h.reconciler.Client, dv.Namespace, sets.New(getDataVolumeClaimName(dv)), 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 DataVolume PVC", ""); err != nil {
return 0, err
}
return defaultRequeue, nil
}
dv2 := dv.DeepCopy()
dv2.Status = cdiv1.DataVolumeStatus{}
data := map[string]string{
"pvcName": getDataVolumeClaimName(dv),
}
return 0, h.reconciler.pendingHelper(ot, dv2, data)
}
func (h *dataVolumeTransferHandler) ReconcileRunning(ot *cdiv1.ObjectTransfer) (time.Duration, error) {
dv := &cdiv1.DataVolume{}
dvExists, err := h.reconciler.getSourceResource(ot, dv)
if err != nil {
return 0, err
}
if dvExists {
return h.deleteDataVolume(ot, dv)
}
pvcTransferName := fmt.Sprintf("pvc-transfer-%s", ot.UID)
pvcTransfer := &cdiv1.ObjectTransfer{}
pvcTransferExists, err := h.reconciler.getResource("", pvcTransferName, pvcTransfer)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
target := &cdiv1.DataVolume{}
targetExists, err := h.reconciler.getTargetResource(ot, target)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
pvcName := ot.Status.Data["pvcName"]
if !targetExists && !pvcTransferExists {
targetNamespace := getTransferTargetNamespace(ot)
targetName := getTransferTargetName(ot)
pvcTransfer = &cdiv1.ObjectTransfer{
ObjectMeta: metav1.ObjectMeta{
Name: pvcTransferName,
Labels: map[string]string{
common.CDILabelKey: common.CDILabelValue,
common.CDIComponentLabel: "",
},
},
Spec: cdiv1.ObjectTransferSpec{
Source: cdiv1.TransferSource{
Kind: "PersistentVolumeClaim",
Namespace: ot.Spec.Source.Namespace,
Name: pvcName,
},
Target: cdiv1.TransferTarget{
Namespace: &targetNamespace,
Name: &targetName,
},
ParentName: &ot.Name,
},
}
util.SetRecommendedLabels(pvcTransfer, h.reconciler.InstallerLabels, "cdi-controller")
if err := h.reconciler.Client.Create(context.TODO(), pvcTransfer); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
pvcTransferExists = true
}
if pvcTransferExists {
if pvcTransfer.Status.Phase != cdiv1.ObjectTransferComplete {
ot.Status.Phase = cdiv1.ObjectTransferRunning
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "PVC transfer in progress", ""); err != nil {
return 0, err
}
return 0, nil
}
pvc := &corev1.PersistentVolumeClaim{}
pvcExists, err := h.reconciler.getTargetResource(pvcTransfer, pvc)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if !pvcExists {
ot.Status.Phase = cdiv1.ObjectTransferError
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Transferred PVC does not exist", ""); err != nil {
return 0, err
}
return 0, nil
}
if err := h.addPopulatedAnnotation(ot, pvc); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
}
if !targetExists {
target = &cdiv1.DataVolume{}
if err := h.reconciler.createObjectTransferTarget(ot, target, nil); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
}
if target.Status.Phase != cdiv1.Succeeded {
ot.Status.Phase = cdiv1.ObjectTransferRunning
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Waiting for target DataVolume", ""); err != nil {
return 0, err
}
return 0, nil
}
if pvcTransferExists && pvcTransfer.DeletionTimestamp == nil {
if err := h.reconciler.Client.Delete(context.TODO(), pvcTransfer); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
}
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
}
func (h *dataVolumeTransferHandler) deleteDataVolume(ot *cdiv1.ObjectTransfer, dv *cdiv1.DataVolume) (time.Duration, error) {
pvc := &corev1.PersistentVolumeClaim{}
pvcExists, err := h.reconciler.getResource(dv.Namespace, getDataVolumeClaimName(dv), pvc)
if err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
if !pvcExists {
if err := h.reconciler.setAndUpdateCompleteCondition(ot, corev1.ConditionFalse, "Source DV has no PVC", ""); err != nil {
return 0, err
}
return 0, nil
}
idx := -1
for i, o := range pvc.OwnerReferences {
if o.Kind == "DataVolume" &&
o.Name == dv.Name &&
o.UID == dv.UID {
idx = i
break
}
}
if idx >= 0 {
os := pvc.OwnerReferences
pvc.OwnerReferences = append(os[0:idx], os[idx+1:]...)
}
_, ok := pvc.Annotations[cc.AnnPopulatedFor]
if ok {
delete(pvc.Annotations, cc.AnnPopulatedFor)
}
if idx >= 0 || ok {
if err := h.reconciler.updateResource(ot, pvc); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
return time.Second, h.reconciler.setCompleteConditionRunning(ot)
}
if err := h.reconciler.Client.Delete(context.TODO(), dv); err != nil {
return 0, h.reconciler.setCompleteConditionError(ot, err)
}
return 0, h.reconciler.setCompleteConditionRunning(ot)
}
func (h *dataVolumeTransferHandler) addPopulatedAnnotation(ot *cdiv1.ObjectTransfer, pvc *corev1.PersistentVolumeClaim) error {
dvName := getTransferTargetName(ot)
if v, ok := pvc.Annotations[cc.AnnPopulatedFor]; ok {
if v == dvName {
return nil
}
return fmt.Errorf("PVC populated for a different DataVolume")
}
if pvc.Annotations == nil {
pvc.Annotations = make(map[string]string)
}
pvc.Annotations[cc.AnnPopulatedFor] = dvName
return h.reconciler.updateResource(ot, pvc)
}
func getDataVolumeClaimName(dv *cdiv1.DataVolume) string {
pvcName, ok := dv.Annotations[cc.AnnPopulatedFor]
if ok {
return pvcName
}
return dv.Name
}