diff --git a/tests/storage/migration.go b/tests/storage/migration.go index 3db50dfcfcee..40d5ea3889e9 100644 --- a/tests/storage/migration.go +++ b/tests/storage/migration.go @@ -22,6 +22,7 @@ package storage import ( "context" "fmt" + "slices" "strconv" "time" @@ -34,12 +35,14 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" virtv1 "kubevirt.io/api/core/v1" "kubevirt.io/client-go/kubecli" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "kubevirt.io/kubevirt/pkg/apimachinery/patch" "kubevirt.io/kubevirt/pkg/controller" "kubevirt.io/kubevirt/pkg/libvmi" "kubevirt.io/kubevirt/pkg/pointer" @@ -203,53 +206,58 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { return vm } - updateVMWithPVC := func(vmName, volName, claim string) { - var replacedIndex int - vm, err := virtClient.VirtualMachine(ns).Get(context.Background(), vmName, metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - // Remove datavolume templates - vm.Spec.DataVolumeTemplates = []virtv1.DataVolumeTemplateSpec{} + updateVMWithPVC := func(vm *virtv1.VirtualMachine, volName, claim string) { // Replace dst pvc - for i, v := range vm.Spec.Template.Spec.Volumes { - if v.Name == volName { - By(fmt.Sprintf("Replacing volume %s with PVC %s", volName, claim)) - vm.Spec.Template.Spec.Volumes[i].VolumeSource.PersistentVolumeClaim = &virtv1.PersistentVolumeClaimVolumeSource{ - PersistentVolumeClaimVolumeSource: k8sv1.PersistentVolumeClaimVolumeSource{ - ClaimName: claim, - }, - } - vm.Spec.Template.Spec.Volumes[i].VolumeSource.DataVolume = nil - replacedIndex = i - break - } - } - vm.Spec.UpdateVolumesStrategy = pointer.P(virtv1.UpdateVolumesStrategyMigration) - vm, err = virtClient.VirtualMachine(ns).Update(context.Background(), vm, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - Expect(vm.Spec.Template.Spec.Volumes[replacedIndex].VolumeSource.PersistentVolumeClaim. - PersistentVolumeClaimVolumeSource.ClaimName).To(Equal(claim)) + i := slices.IndexFunc(vm.Spec.Template.Spec.Volumes, func(volume virtv1.Volume) bool { + return volume.Name == volName + }) + Expect(i).To(BeNumerically(">", -1)) + By(fmt.Sprintf("Replacing volume %s with PVC %s", volName, claim)) + + updatedVolume := virtv1.Volume{ + Name: volName, + VolumeSource: virtv1.VolumeSource{PersistentVolumeClaim: &virtv1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: k8sv1.PersistentVolumeClaimVolumeSource{ + ClaimName: claim, + }}}} + + p, err := patch.New( + patch.WithReplace("/spec/dataVolumeTemplates", []virtv1.DataVolumeTemplateSpec{}), + patch.WithReplace(fmt.Sprintf("/spec/template/spec/volumes/%d", i), updatedVolume), + patch.WithReplace("/spec/updateVolumesStrategy", virtv1.UpdateVolumesStrategyMigration), + ).GeneratePayload() + Expect(err).ToNot(HaveOccurred()) + vm, err = virtClient.VirtualMachine(vm.Namespace).Patch(context.Background(), vm.Name, types.JSONPatchType, p, metav1.PatchOptions{}) + Expect(err).ToNot(HaveOccurred()) + Expect(vm.Spec.Template.Spec.Volumes[i].VolumeSource.PersistentVolumeClaim. + PersistentVolumeClaimVolumeSource.ClaimName).To(Equal(claim)) } // TODO: right now, for simplicity, this function assumes the DV in the first position in the datavolumes templata list. Otherwise, we need // to pass the old name of the DV to be replaces. - updateVMWithDV := func(vmName, volName, name string) { - var replacedIndex int - vm, err := virtClient.VirtualMachine(ns).Get(context.Background(), vmName, metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - vm.Spec.DataVolumeTemplates[0].Name = name - for i, v := range vm.Spec.Template.Spec.Volumes { - if v.Name == volName { - vm.Spec.Template.Spec.Volumes[i].VolumeSource.DataVolume = &virtv1.DataVolumeSource{ - Name: name, - } - replacedIndex = i - break - } - } - vm.Spec.UpdateVolumesStrategy = pointer.P(virtv1.UpdateVolumesStrategyMigration) - vm, err = virtClient.VirtualMachine(ns).Update(context.Background(), vm, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - Expect(vm.Spec.Template.Spec.Volumes[replacedIndex].VolumeSource.DataVolume.Name).To(Equal(name)) + updateVMWithDV := func(vm *virtv1.VirtualMachine, volName, name string) { + i := slices.IndexFunc(vm.Spec.Template.Spec.Volumes, func(volume virtv1.Volume) bool { + return volume.Name == volName + }) + Expect(i).To(BeNumerically(">", -1)) + By(fmt.Sprintf("Replacing volume %s with DV %s", volName, name)) + + updatedVolume := virtv1.Volume{ + Name: volName, + VolumeSource: virtv1.VolumeSource{DataVolume: &virtv1.DataVolumeSource{ + Name: name, + }}} + + p, err := patch.New( + patch.WithReplace("/spec/dataVolumeTemplates/0/metadata/name", name), + patch.WithReplace(fmt.Sprintf("/spec/template/spec/volumes/%d", i), updatedVolume), + patch.WithReplace("/spec/updateVolumesStrategy", virtv1.UpdateVolumesStrategyMigration), + ).GeneratePayload() + Expect(err).ToNot(HaveOccurred()) + vm, err = virtClient.VirtualMachine(vm.Namespace).Patch(context.Background(), vm.Name, types.JSONPatchType, p, metav1.PatchOptions{}) + Expect(err).ToNot(HaveOccurred()) + + Expect(vm.Spec.Template.Spec.Volumes[i].VolumeSource.DataVolume.Name).To(Equal(name)) } BeforeEach(func() { @@ -271,7 +279,7 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { Fail("Unrecognized mode") } By("Update volumes") - updateVMWithPVC(vm.Name, volName, destPVC) + updateVMWithPVC(vm, volName, destPVC) Eventually(func() bool { vmi, err := virtClient.VirtualMachineInstance(ns).Get(context.Background(), vm.Name, metav1.GetOptions{}) @@ -290,7 +298,7 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { vm := createVMWithDV(createDV(), volName) destDV := createBlankDV() By("Update volumes") - updateVMWithDV(vm.Name, volName, destDV.Name) + updateVMWithDV(vm, volName, destDV.Name) Eventually(func() bool { vmi, err := virtClient.VirtualMachineInstance(ns).Get(context.Background(), vm.Name, metav1.GetOptions{}) @@ -322,7 +330,7 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { libwait.WaitForSuccessfulVMIStart(vmi) By("Update volumes") - updateVMWithPVC(vm.Name, volName, destPVC) + updateVMWithPVC(vm, volName, destPVC) Eventually(func() bool { vmi, err := virtClient.VirtualMachineInstance(ns).Get(context.Background(), vm.Name, metav1.GetOptions{}) @@ -346,11 +354,11 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { // Create dest PVC createUnschedulablePVC(destPVC, ns, size) By("Update volumes") - updateVMWithPVC(vm.Name, volName, destPVC) + updateVMWithPVC(vm, volName, destPVC) waitMigrationToExist(vm.Name, ns) waitVMIToHaveVolumeChangeCond(vm.Name, ns) By("Cancel the volume migration") - updateVMWithPVC(vm.Name, volName, dv.Name) + updateVMWithPVC(vm, volName, dv.Name) // After the volume migration abortion the VMI should have: // 1. the source volume restored // 2. condition VolumesChange set to false @@ -377,7 +385,7 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { vm := createVMWithDV(createDV(), volName) createSmallImageForDestinationMigration(vm, destPVC, size) By("Update volume") - updateVMWithPVC(vm.Name, volName, destPVC) + updateVMWithPVC(vm, volName, destPVC) // let the workload updater creates some migration time.Sleep(2 * time.Minute) ls := labels.Set{virtv1.VolumesUpdateMigration: vm.Name} @@ -413,7 +421,7 @@ var _ = SIGDescribe("[Serial]Volumes update with migration", Serial, func() { Eventually(matcher.ThisVM(vm), 360*time.Second, 1*time.Second).Should(matcher.BeReady()) libwait.WaitForSuccessfulVMIStart(vmi) By("Update volumes") - updateVMWithDV(vm.Name, volName, destDV.Name) + updateVMWithDV(vm, volName, destDV.Name) Eventually(func() []virtv1.VirtualMachineCondition { vm, err := virtClient.VirtualMachine(vm.Namespace).Get(context.Background(), vm.Name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred())