containerized-data-importer/pkg/controller/transfer/dv.go
akalenyu 2254cf0c1f
Add relationship labels (#1864)
Users don't want 👽 resources in clusters,
and we should also be able to tell if were part of a broader installation.

Note:
- Operator created resources were handled in https://github.com/kubevirt/controller-lifecycle-operator-sdk/pull/18
as these labels will be common to all resources deployed by the HCO.
- Now that the controller is guaranteed to have the labels, we can set env vars
that reference the label values (fieldRef) to spare calling GET on the CR in the controllers.
(thanks mhenriks).

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
2021-07-28 20:05:24 +02:00

260 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/pkg/apis/core/v1beta1"
"kubevirt.io/containerized-data-importer/pkg/common"
cdicontroller "kubevirt.io/containerized-data-importer/pkg/controller"
"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 := cdicontroller.GetPodsUsingPVCs(h.reconciler.Client, dv.Namespace, sets.NewString(cdicontroller.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": cdicontroller.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, cdicontroller.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[cdicontroller.AnnPopulatedFor]
if ok {
delete(pvc.Annotations, cdicontroller.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[cdicontroller.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[cdicontroller.AnnPopulatedFor] = dvName
return h.reconciler.updateResource(ot, pvc)
}