Skip to content

Commit 5ea7669

Browse files
committed
Update DataVolumeTemplate storage class during
storage class migration. Signed-off-by: Alexander Wels <awels@redhat.com>
1 parent 5045d1b commit 5ea7669

2 files changed

Lines changed: 156 additions & 16 deletions

File tree

pkg/controller/directvolumemigration/vm.go

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
prometheusapi "github.com/prometheus/client_golang/api"
2020
prometheusv1 "github.com/prometheus/client_golang/api/prometheus/v1"
2121
corev1 "k8s.io/api/core/v1"
22+
storagev1 "k8s.io/api/storage/v1"
2223
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2324
"k8s.io/apimachinery/pkg/api/meta"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -28,16 +29,19 @@ import (
2829
"k8s.io/client-go/rest"
2930
"k8s.io/utils/ptr"
3031
virtv1 "kubevirt.io/api/core/v1"
32+
3133
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
3234
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
3335
)
3436

3537
const (
36-
prometheusURLKey = "PROMETHEUS_URL"
37-
prometheusRoute = "prometheus-k8s"
38-
progressQuery = "kubevirt_vmi_migration_data_processed_bytes{name=\"%s\"} / (kubevirt_vmi_migration_data_processed_bytes{name=\"%s\"} + kubevirt_vmi_migration_data_remaining_bytes{name=\"%s\"}) * 100"
39-
VMIKind = "VirtualMachineInstance"
40-
PodKind = "Pod"
38+
prometheusURLKey = "PROMETHEUS_URL"
39+
prometheusRoute = "prometheus-k8s"
40+
progressQuery = "kubevirt_vmi_migration_data_processed_bytes{name=\"%s\"} / (kubevirt_vmi_migration_data_processed_bytes{name=\"%s\"} + kubevirt_vmi_migration_data_remaining_bytes{name=\"%s\"}) * 100"
41+
VMIKind = "VirtualMachineInstance"
42+
PodKind = "Pod"
43+
defaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
44+
defaultK8sStorageClass = "storageclass.kubernetes.io/is-default-class"
4145
)
4246

4347
var (
@@ -455,25 +459,35 @@ func updateVM(client k8sclient.Client, vm *virtv1.VirtualMachine, sourceVolumes,
455459
log.V(5).Info("Setting volume migration strategy to migration", "vm", vmCopy.Name)
456460
vmCopy.Spec.UpdateVolumesStrategy = ptr.To[virtv1.UpdateVolumesStrategy](virtv1.UpdateVolumesStrategyMigration)
457461

462+
volumeNameToPVCMap := make(map[string]string)
463+
458464
for i := 0; i < len(sourceVolumes); i++ {
459465
// Check if we need to update DataVolumeTemplates.
460-
for j, dvTemplate := range vmCopy.Spec.DataVolumeTemplates {
461-
if dvTemplate.Name == sourceVolumes[i] {
462-
log.V(5).Info("Updating DataVolumeTemplate", "source", sourceVolumes[i], "target", targetVolumes[i])
463-
vmCopy.Spec.DataVolumeTemplates[j].Name = targetVolumes[i]
464-
}
465-
}
466466
for j, volume := range vm.Spec.Template.Spec.Volumes {
467467
if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == sourceVolumes[i] {
468468
log.V(5).Info("Updating PersistentVolumeClaim", "source", sourceVolumes[i], "target", targetVolumes[i])
469469
vmCopy.Spec.Template.Spec.Volumes[j].PersistentVolumeClaim.ClaimName = targetVolumes[i]
470+
volumeNameToPVCMap[sourceVolumes[i]] = targetVolumes[i]
470471
}
471472
if volume.DataVolume != nil && volume.DataVolume.Name == sourceVolumes[i] {
472473
log.V(5).Info("Updating DataVolume", "source", sourceVolumes[i], "target", targetVolumes[i])
473474
if err := CreateNewAdoptionDataVolume(client, sourceVolumes[i], targetVolumes[i], vmCopy.Namespace, log); err != nil {
474475
return err
475476
}
476477
vmCopy.Spec.Template.Spec.Volumes[j].DataVolume.Name = targetVolumes[i]
478+
volumeNameToPVCMap[sourceVolumes[i]] = targetVolumes[i]
479+
}
480+
}
481+
for j, dvTemplate := range vmCopy.Spec.DataVolumeTemplates {
482+
if dvTemplate.Name == sourceVolumes[i] {
483+
log.V(5).Info("Updating DataVolumeTemplate", "source", sourceVolumes[i], "target", targetVolumes[i])
484+
vmCopy.Spec.DataVolumeTemplates[j].Name = targetVolumes[i]
485+
pvcName := volumeNameToPVCMap[sourceVolumes[i]]
486+
sc, err := getStorageClassFromName(client, pvcName, vmCopy.Namespace)
487+
if err != nil {
488+
return err
489+
}
490+
vmCopy.Spec.DataVolumeTemplates[j].Spec.Storage.StorageClassName = ptr.To(sc)
477491
}
478492
}
479493
}
@@ -488,6 +502,35 @@ func updateVM(client k8sclient.Client, vm *virtv1.VirtualMachine, sourceVolumes,
488502
return nil
489503
}
490504

505+
func getStorageClassFromName(client k8sclient.Client, name, namespace string) (string, error) {
506+
volume := &corev1.PersistentVolumeClaim{}
507+
if err := client.Get(context.TODO(), k8sclient.ObjectKey{Namespace: namespace, Name: name}, volume); err != nil {
508+
return "", err
509+
}
510+
if volume.Spec.StorageClassName == nil {
511+
// Find the default storage class
512+
scList := &storagev1.StorageClassList{}
513+
if err := client.List(context.TODO(), scList); err != nil {
514+
return "", err
515+
}
516+
defaultStorageClass := ""
517+
for _, sc := range scList.Items {
518+
if sc.Annotations != nil && sc.Annotations[defaultK8sStorageClass] == "true" {
519+
defaultStorageClass = sc.Name
520+
}
521+
if sc.Annotations != nil && sc.Annotations[defaultVirtStorageClass] == "true" {
522+
return sc.Name, nil
523+
}
524+
}
525+
if defaultStorageClass != "" {
526+
return defaultStorageClass, nil
527+
}
528+
// No default storage class found, return blank
529+
return "", nil
530+
}
531+
return *volume.Spec.StorageClassName, nil
532+
}
533+
491534
func createBlankDataVolumeFromPVC(client k8sclient.Client, targetPvc *corev1.PersistentVolumeClaim) error {
492535
dv := &cdiv1.DataVolume{
493536
ObjectMeta: metav1.ObjectMeta{

pkg/controller/directvolumemigration/vm_test.go

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
prometheusv1 "github.com/prometheus/client_golang/api/prometheus/v1"
2222
"github.com/prometheus/common/model"
2323
corev1 "k8s.io/api/core/v1"
24+
storagev1 "k8s.io/api/storage/v1"
2425
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
"k8s.io/client-go/rest"
@@ -31,11 +32,15 @@ import (
3132
)
3233

3334
const (
34-
sourcePVC = "source-pvc"
35-
sourceNs = "source-ns"
36-
targetPVC = "target-pvc"
37-
targetNs = "target-ns"
38-
targetDv = "target-dv"
35+
sourcePVC = "source-pvc"
36+
sourceNs = "source-ns"
37+
targetPVC = "target-pvc"
38+
targetNs = "target-ns"
39+
targetDv = "target-dv"
40+
testPVCName = "test-pvc"
41+
testStorageClass = "test-sc"
42+
testDefaultStorageClass = "test-default-sc"
43+
testVirtDefaultStorageClass = "test-virt-default-sc"
3944
)
4045

4146
func TestTask_startLiveMigrations(t *testing.T) {
@@ -1428,6 +1433,60 @@ func TestTaskBuildSourcePrometheusEndPointURL(t *testing.T) {
14281433
}
14291434
}
14301435

1436+
func TestGetStorageClassFromName(t *testing.T) {
1437+
tests := []struct {
1438+
name string
1439+
client compat.Client
1440+
expectedError bool
1441+
expectedSc string
1442+
}{
1443+
{
1444+
name: "no pvcs, no storage class, should return blank",
1445+
client: getFakeClientWithObjs(),
1446+
expectedError: false,
1447+
expectedSc: "",
1448+
},
1449+
{
1450+
name: "pvcs, with storage class, should return name",
1451+
client: getFakeClientWithObjs(createPVC(testPVCName, testNamespace)),
1452+
expectedError: false,
1453+
expectedSc: testStorageClass,
1454+
},
1455+
{
1456+
name: "pvcs, no storage class, no default storage class return blank",
1457+
client: getFakeClientWithObjs(createNoStorageClassPVC(testPVCName, testNamespace)),
1458+
expectedError: false,
1459+
expectedSc: "",
1460+
},
1461+
{
1462+
name: "pvcs, no storage class, default storage class, should return name",
1463+
client: getFakeClientWithObjs(createNoStorageClassPVC(testPVCName, testNamespace), createDefaultStorageClass(testDefaultStorageClass)),
1464+
expectedError: false,
1465+
expectedSc: testDefaultStorageClass,
1466+
},
1467+
{
1468+
name: "pvcs, no storage class, virt default storage class, should return name",
1469+
client: getFakeClientWithObjs(createNoStorageClassPVC(testPVCName, testNamespace), createDefaultStorageClass(testDefaultStorageClass), createVirtDefaultStorageClass(testVirtDefaultStorageClass)),
1470+
expectedError: false,
1471+
expectedSc: testVirtDefaultStorageClass,
1472+
},
1473+
}
1474+
for _, tt := range tests {
1475+
t.Run(tt.name, func(t *testing.T) {
1476+
sc, err := getStorageClassFromName(tt.client, testPVCName, testNamespace)
1477+
if tt.expectedError {
1478+
if err == nil {
1479+
t.Errorf("expected error but got nil")
1480+
t.FailNow()
1481+
}
1482+
}
1483+
if sc != tt.expectedSc {
1484+
t.Errorf("expected %s, got %s", tt.expectedSc, sc)
1485+
}
1486+
})
1487+
}
1488+
}
1489+
14311490
func getFakeClientWithObjs(obj ...k8sclient.Object) compat.Client {
14321491
client, _ := fakecompat.NewFakeClient(obj...)
14331492
return client
@@ -1579,6 +1638,44 @@ func createRoute(name, namespace, url string) *routev1.Route {
15791638
}
15801639
}
15811640

1641+
func createPVC(name, namespace string) *corev1.PersistentVolumeClaim {
1642+
pvc := createNoStorageClassPVC(name, namespace)
1643+
pvc.Spec.StorageClassName = ptr.To(testStorageClass)
1644+
return pvc
1645+
}
1646+
1647+
func createNoStorageClassPVC(name, namespace string) *corev1.PersistentVolumeClaim {
1648+
return &corev1.PersistentVolumeClaim{
1649+
ObjectMeta: metav1.ObjectMeta{
1650+
Name: name,
1651+
Namespace: namespace,
1652+
},
1653+
}
1654+
}
1655+
func createStorageClass(name string) *storagev1.StorageClass {
1656+
return &storagev1.StorageClass{
1657+
ObjectMeta: metav1.ObjectMeta{
1658+
Name: name,
1659+
},
1660+
}
1661+
}
1662+
1663+
func createDefaultStorageClass(name string) *storagev1.StorageClass {
1664+
sc := createStorageClass(name)
1665+
sc.Annotations = map[string]string{
1666+
defaultK8sStorageClass: "true",
1667+
}
1668+
return sc
1669+
}
1670+
1671+
func createVirtDefaultStorageClass(name string) *storagev1.StorageClass {
1672+
sc := createStorageClass(name)
1673+
sc.Annotations = map[string]string{
1674+
defaultVirtStorageClass: "true",
1675+
}
1676+
return sc
1677+
}
1678+
15821679
type mockPrometheusClient struct {
15831680
fakeUrl string
15841681
responseBody string

0 commit comments

Comments
 (0)