containerized-data-importer/pkg/controller/transfer/pvc_test.go
Alex Kalenyuk 31d12e426e
update k8s & related libraries to 1.28 (#3078)
* Bump k8s/OpenShift/ctrl-runtime/lifecycle-sdk & make deps-update

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Operator: adapt for dependency bump

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Controller: adapt watch calls for dependency bump

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Controller: adapt to ctrl-runtime's cache API changes

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Operator: fix unit tests by deleting resources properly in fake client

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Controller: fix unit tests by deleting resources properly in fake client

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Controller: adapt to fake client honoring status subresource

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Fix codegen script & make generate

There are some issues in the new script, so we
will still use the deprecated one.
More context in f4d1a5431b

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Functests: Adapt to NamespacedName now implementing MarshalLog

ns/name -> {"name":"name","namespace":"ns"}

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* Functests & API server: address deprecation of wait.PollImmediate

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

---------

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
2024-01-23 17:52:05 +01:00

542 lines
16 KiB
Go

/*
Copyright 2020 The CDI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package transfer_test
import (
"context"
"encoding/json"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
)
var _ = Describe("PVC Transfer Tests", func() {
var _ = Describe("PVC Transfer Tests", func() {
It("Should initialize", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferEmpty)
r := createReconciler(xfer)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Finalizers).To(HaveLen(1))
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "Initializing", "")
})
It("Should handle no source", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
r := createReconciler(xfer)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "No source", "")
})
It("Should handle unbound source", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
r := createReconciler(xfer, createUnboundPVC())
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "PVC not bound", "")
})
It("Should handle PVC with finalizer", func() {
f := "snapshot.storage.kubernetes.io/pvc-as-source-protection"
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
pvc := createBoundPVC()
pvc.Finalizers = append(pvc.Finalizers, f)
r := createReconciler(xfer, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "PVC has finalizer: "+f, "")
})
It("Should handle PV not bound", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
pvc := createBoundPVC()
pv := sourcePV()
pv.Spec.ClaimRef = nil
r := createReconciler(xfer, pvc, pv)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "PV not bound", "")
})
It("Should handle pod using PVC", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
pvc := createBoundPVC()
pv := sourcePV()
r := createReconciler(xfer, pvc, pv, createPod(pvc.Name))
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "Pods using PVC", "")
})
It("Should handle another transfer", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
pvc := createBoundPVC()
pvc.Annotations = map[string]string{
"cdi.kubevirt.io/objectTransferName": "baz",
}
r := createReconciler(xfer, sourcePV(), pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferPending))
checkCompleteFalse(xfer, "Source in use by another transfer", "baz")
})
It("Should cancel", func() {
t := metav1.Now()
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
xfer.Finalizers = []string{
"cdi.kubevirt.io/objectTransfer",
}
xfer.DeletionTimestamp = &t
r := createReconciler(xfer)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
})
It("Should become running", func() {
xfer := pvcTransfer(cdiv1.ObjectTransferPending)
r := createReconciler(xfer, sourcePV(), createBoundPVC())
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
Expect(xfer.Status.Data).To(Equal(pvcTransferRunningWithBoundPVC().Status.Data))
checkCompleteFalse(xfer, "Running", "")
})
It("Should handle missing pv name", func() {
xfer := pvcTransferRunning()
delete(xfer.Status.Data, "pvName")
r := createReconciler(xfer, createBoundPVC())
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferError))
checkCompleteFalse(xfer, "PV name missing", "")
})
It("Should error missing pv", func() {
xfer := pvcTransferRunning()
r := createReconciler(xfer)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).To(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferError))
checkCompleteFalse(xfer, "Error", "persistentvolumes \"source-pv\" not found")
})
It("Should store reclaim", func() {
xfer := pvcTransferRunning()
r := createReconciler(xfer, sourcePV())
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
Expect(xfer.Status.Data["pvReclaim"]).To(Equal("Delete"))
checkCompleteFalse(xfer, "Running", "")
})
It("Should update PV reclaim", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pvc := createBoundPVC()
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", pv.Name, pv)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", pvc.Name, pvc)
Expect(err).To(HaveOccurred())
Expect(pv.Spec.PersistentVolumeReclaimPolicy).To(Equal(corev1.PersistentVolumeReclaimRetain))
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should delete source pvc", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pvc := createBoundPVC()
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", pv.Name, pv)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, pvc.Namespace, pvc.Name, pvc)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should update claimref", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
r := createReconciler(xfer, pv)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", pv.Name, pv)
Expect(err).ToNot(HaveOccurred())
Expect(pv.Spec.ClaimRef).ToNot(BeNil())
Expect(&pv.Spec.ClaimRef.Namespace).To(Equal(xfer.Spec.Target.Namespace))
Expect(&pv.Spec.ClaimRef.Name).To(Equal(xfer.Spec.Target.Name))
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should create target", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pv.Spec.ClaimRef = nil
pvc := &corev1.PersistentVolumeClaim{}
r := createReconciler(xfer, pv)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "target-ns", "target-pvc", pvc)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should create target even when a new source appears", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pv.Spec.ClaimRef = nil
newSource := createBoundPVC()
newSource.Spec.VolumeName = "somethingelse"
pvc := &corev1.PersistentVolumeClaim{}
r := createReconciler(xfer, pv, newSource)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "target-ns", "target-pvc", pvc)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should wait for target to be bound", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pv.Spec.ClaimRef = nil
pvc := createUnboundPVC()
pvc.Namespace = "target-ns"
pvc.Name = "target-pvc"
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Waiting for target to be bound", "")
})
It("Should error if PV gets bound to something else", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pv.Spec.ClaimRef.Namespace = "target-ns"
pv.Spec.ClaimRef.Name = "baz"
pvc := createBoundPVC()
pvc.Namespace = "target-ns"
pvc.Name = "target-pvc"
pvc.Spec.VolumeName = pv.Name
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).To(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferError))
checkCompleteFalse(xfer, "PV bound to wrong PVC", "")
})
It("Should update PV retain", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRetain
pv.Spec.ClaimRef.Namespace = "target-ns"
pv.Spec.ClaimRef.Name = "target-pvc"
pvc := createBoundPVC()
pvc.Namespace = "target-ns"
pvc.Name = "target-pvc"
pvc.Spec.VolumeName = pv.Name
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", pv.Name, pv)
Expect(err).ToNot(HaveOccurred())
Expect(pv.Spec.PersistentVolumeReclaimPolicy).To(Equal(corev1.PersistentVolumeReclaimDelete))
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferRunning))
checkCompleteFalse(xfer, "Running", "")
})
It("Should complete transfer", func() {
xfer := pvcTransferRunning()
xfer.Status.Data["pvReclaim"] = "Delete"
pv := sourcePV()
pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimDelete
pv.Spec.ClaimRef.Namespace = "target-ns"
pv.Spec.ClaimRef.Name = "target-pvc"
pvc := createBoundPVC()
pvc.Namespace = "target-ns"
pvc.Name = "target-pvc"
pvc.Spec.VolumeName = pv.Name
r := createReconciler(xfer, pv, pvc)
_, err := r.Reconcile(context.TODO(), rr(xfer.Name))
Expect(err).ToNot(HaveOccurred())
err = getResource(r.Client, "", xfer.Name, xfer)
Expect(err).ToNot(HaveOccurred())
Expect(xfer.Status.Phase).To(Equal(cdiv1.ObjectTransferComplete))
checkCompleteTrue(xfer)
})
})
})
func createPod(pvcName string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "source-ns",
Name: "source-pod",
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "vol",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvcName,
},
},
},
},
},
}
}
func createUnboundPVC() *corev1.PersistentVolumeClaim {
return &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Namespace: "source-ns",
Name: "source-pvc",
},
}
}
func createBoundPVC() *corev1.PersistentVolumeClaim {
pvc := createUnboundPVC()
pvc.Spec.VolumeName = "source-pv"
pvc.Status.Phase = corev1.ClaimBound
return pvc
}
func pvcTransfer(phase cdiv1.ObjectTransferPhase) *cdiv1.ObjectTransfer {
targetNamespace := "target-ns"
targetName := "target-pvc"
return &cdiv1.ObjectTransfer{
ObjectMeta: metav1.ObjectMeta{
Name: "pvcTransfer",
UID: types.UID("uid-pvcTransfer"),
},
Spec: cdiv1.ObjectTransferSpec{
Source: cdiv1.TransferSource{
Kind: "PersistentVolumeClaim",
Name: "source-pvc",
Namespace: "source-ns",
},
Target: cdiv1.TransferTarget{
Namespace: &targetNamespace,
Name: &targetName,
},
},
Status: cdiv1.ObjectTransferStatus{
Phase: phase,
},
}
}
func pvcTransferRunning() *cdiv1.ObjectTransfer {
t := pvcTransfer(cdiv1.ObjectTransferRunning)
pvc := createBoundPVC()
pvc.Kind = "PersistentVolumeClaim"
pvc.APIVersion = "v1"
pvc.ResourceVersion = "1000"
pvc.Annotations = map[string]string{
"cdi.kubevirt.io/objectTransferName": "pvcTransfer",
}
pvc.Status = corev1.PersistentVolumeClaimStatus{}
bs, _ := json.Marshal(pvc)
t.Status.Data = map[string]string{
"source": string(bs),
"pvName": "source-pv",
}
return t
}
func pvcTransferRunningWithBoundPVC() *cdiv1.ObjectTransfer {
t := pvcTransfer(cdiv1.ObjectTransferRunning)
pvc := createBoundPVC()
pvc.Kind = "PersistentVolumeClaim"
pvc.APIVersion = "v1"
pvc.ResourceVersion = "1000"
pvc.Annotations = map[string]string{
"cdi.kubevirt.io/objectTransferName": "pvcTransfer",
}
pvc.Status = corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound}
bs, _ := json.Marshal(pvc)
t.Status.Data = map[string]string{
"source": string(bs),
"pvName": "source-pv",
}
return t
}
func sourcePV() *corev1.PersistentVolume {
return &corev1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "source-pv",
},
Spec: corev1.PersistentVolumeSpec{
ClaimRef: &corev1.ObjectReference{
Namespace: "source-ns",
Name: "source-pvc",
},
PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete,
},
}
}