From bd3aa5a7d1105f75cea54628ce023ec34c279d6b Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Fri, 19 Nov 2021 01:53:57 +0800 Subject: [PATCH 1/7] fix statefulsets volumeClaimTemplates storageClassName after use Changing PV/PVC Storage Classes Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/kuberesource/kuberesource.go | 1 + pkg/restore/restore.go | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/pkg/kuberesource/kuberesource.go b/pkg/kuberesource/kuberesource.go index a515a70fff..94e106b2af 100644 --- a/pkg/kuberesource/kuberesource.go +++ b/pkg/kuberesource/kuberesource.go @@ -34,4 +34,5 @@ var ( VolumeSnapshotClasses = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotclasses"} VolumeSnapshots = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshots"} VolumeSnapshotContents = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotcontents"} + StatefulSets = schema.GroupResource{Group: "apps", Resource: "statefulsets"} ) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 846d71c780..18ca9b8856 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -20,8 +20,12 @@ import ( go_context "context" "encoding/json" "fmt" + "github.com/vmware-tanzu/velero/pkg/plugin/framework" "io" "io/ioutil" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "path/filepath" "sort" "strings" @@ -1224,6 +1228,43 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso } } + // change sts volumeClaimTemplates storageClassName + if groupResource == kuberesource.StatefulSets { + sts := new(appsv1.StatefulSet) + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts); err != nil { + errs.Add(namespace, err) + return warnings, errs + } + + clientset, err := getClient() + if err != nil { + errs.Add(namespace, errors.Errorf("try to get change-storage-class configmap clientset failed: %s", err.Error())) + return warnings, errs + } + + // get new storageClass + configMap, err := getPluginConfig(framework.PluginKindRestoreItemAction, "velero.io/change-storage-class", clientset.CoreV1().ConfigMaps("velero")) + if err != nil { + errs.Add(namespace, err) + return warnings, errs + } + + if len(sts.Spec.VolumeClaimTemplates) > 0 && configMap != nil && len(configMap.Data) > 0 { + for index, pvc := range sts.Spec.VolumeClaimTemplates { + if newStorageClass, ok := configMap.Data[*pvc.Spec.StorageClassName]; ok { + sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass + } + } + + newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts) + if err != nil { + errs.Add(namespace, err) + return warnings, errs + } + obj.Object = newObj + } + } + // Necessary because we may have remapped the namespace if the namespace is // blank, don't create the key. originalNamespace := obj.GetNamespace() @@ -1841,3 +1882,15 @@ func (ctx *restoreContext) getSelectedRestoreableItems(resource, targetNamespace } return restorable, warnings, errs } + +func getClient() (clientset *kubernetes.Clientset, err error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, err + } + clientset, err = kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + return clientset, nil +} \ No newline at end of file From b85bba00ee77c8458b11a64f9795aeee5d7309bc Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Sun, 28 Nov 2021 17:44:59 +0800 Subject: [PATCH 2/7] Fix (vmware-tanzu#4373) Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- changelogs/unreleased/4375-Box-Cube | 1 + pkg/kuberesource/kuberesource.go | 1 - pkg/restore/change_storageclass_action.go | 79 +++++++++++++++-------- pkg/restore/restore.go | 39 ----------- 4 files changed, 53 insertions(+), 67 deletions(-) create mode 100644 changelogs/unreleased/4375-Box-Cube diff --git a/changelogs/unreleased/4375-Box-Cube b/changelogs/unreleased/4375-Box-Cube new file mode 100644 index 0000000000..fd1b1b231e --- /dev/null +++ b/changelogs/unreleased/4375-Box-Cube @@ -0,0 +1 @@ +Fix statefulsets volumeClaimTemplates storageClassName when use Changing PV/PVC Storage Classes \ No newline at end of file diff --git a/pkg/kuberesource/kuberesource.go b/pkg/kuberesource/kuberesource.go index 94e106b2af..a515a70fff 100644 --- a/pkg/kuberesource/kuberesource.go +++ b/pkg/kuberesource/kuberesource.go @@ -34,5 +34,4 @@ var ( VolumeSnapshotClasses = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotclasses"} VolumeSnapshots = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshots"} VolumeSnapshotContents = schema.GroupResource{Group: "snapshot.storage.k8s.io", Resource: "volumesnapshotcontents"} - StatefulSets = schema.GroupResource{Group: "apps", Resource: "statefulsets"} ) diff --git a/pkg/restore/change_storageclass_action.go b/pkg/restore/change_storageclass_action.go index effa83dbcb..6bed0809bb 100644 --- a/pkg/restore/change_storageclass_action.go +++ b/pkg/restore/change_storageclass_action.go @@ -21,8 +21,10 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" storagev1client "k8s.io/client-go/kubernetes/typed/storage/v1" @@ -55,7 +57,7 @@ func NewChangeStorageClassAction( // be run for. func (a *ChangeStorageClassAction) AppliesTo() (velero.ResourceSelector, error) { return velero.ResourceSelector{ - IncludedResources: []string{"persistentvolumeclaims", "persistentvolumes"}, + IncludedResources: []string{"persistentvolumeclaims", "persistentvolumes", "statefulsets"}, }, nil } @@ -87,32 +89,55 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut "name": obj.GetName(), }) - // use the unstructured helpers here since this code is for both PVs and PVCs, and the - // field names are the same for both types. - storageClass, _, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "storageClassName") - if err != nil { - return nil, errors.Wrap(err, "error getting item's spec.storageClassName") - } - if storageClass == "" { - log.Debug("Item has no storage class specified") - return velero.NewRestoreItemActionExecuteOutput(input.Item), nil - } - - newStorageClass, ok := config.Data[storageClass] - if !ok { - log.Debugf("No mapping found for storage class %s", storageClass) - return velero.NewRestoreItemActionExecuteOutput(input.Item), nil - } - - // validate that new storage class exists - if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil { - return nil, errors.Wrapf(err, "error getting storage class %s from API", newStorageClass) - } - - log.Infof("Updating item's storage class name to %s", newStorageClass) - - if err := unstructured.SetNestedField(obj.UnstructuredContent(), newStorageClass, "spec", "storageClassName"); err != nil { - return nil, errors.Wrap(err, "unable to set item's spec.storageClassName") + // change sts volumeClaimTemplates storageClassName + if obj.GetKind() == "StatefulSet" { + sts := new(appsv1.StatefulSet) + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts); err != nil { + return nil, err + } + + if len(sts.Spec.VolumeClaimTemplates) > 0 { + for index, pvc := range sts.Spec.VolumeClaimTemplates { + if newStorageClass, ok := config.Data[*pvc.Spec.StorageClassName]; ok { + log.Infof("Updating %s's storage class name to %s",sts.Name, newStorageClass) + sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass + } + } + + newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts) + if err != nil { + return nil, errors.Wrap(err, "converts obj to sts failed") + } + obj.Object = newObj + } + } else { + // use the unstructured helpers here since this code is for both PVs and PVCs, and the + // field names are the same for both types. + storageClass, _, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "storageClassName") + if err != nil { + return nil, errors.Wrap(err, "error getting item's spec.storageClassName") + } + if storageClass == "" { + log.Debug("Item has no storage class specified") + return velero.NewRestoreItemActionExecuteOutput(input.Item), nil + } + + newStorageClass, ok := config.Data[storageClass] + if !ok { + log.Debugf("No mapping found for storage class %s", storageClass) + return velero.NewRestoreItemActionExecuteOutput(input.Item), nil + } + + // validate that new storage class exists + if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil { + return nil, errors.Wrapf(err, "error getting storage class %s from API", newStorageClass) + } + + log.Infof("Updating item's storage class name to %s", newStorageClass) + + if err := unstructured.SetNestedField(obj.UnstructuredContent(), newStorageClass, "spec", "storageClassName"); err != nil { + return nil, errors.Wrap(err, "unable to set item's spec.storageClassName") + } } return velero.NewRestoreItemActionExecuteOutput(obj), nil diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 18ca9b8856..0529eb0fee 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -20,10 +20,8 @@ import ( go_context "context" "encoding/json" "fmt" - "github.com/vmware-tanzu/velero/pkg/plugin/framework" "io" "io/ioutil" - appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "path/filepath" @@ -1228,43 +1226,6 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso } } - // change sts volumeClaimTemplates storageClassName - if groupResource == kuberesource.StatefulSets { - sts := new(appsv1.StatefulSet) - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts); err != nil { - errs.Add(namespace, err) - return warnings, errs - } - - clientset, err := getClient() - if err != nil { - errs.Add(namespace, errors.Errorf("try to get change-storage-class configmap clientset failed: %s", err.Error())) - return warnings, errs - } - - // get new storageClass - configMap, err := getPluginConfig(framework.PluginKindRestoreItemAction, "velero.io/change-storage-class", clientset.CoreV1().ConfigMaps("velero")) - if err != nil { - errs.Add(namespace, err) - return warnings, errs - } - - if len(sts.Spec.VolumeClaimTemplates) > 0 && configMap != nil && len(configMap.Data) > 0 { - for index, pvc := range sts.Spec.VolumeClaimTemplates { - if newStorageClass, ok := configMap.Data[*pvc.Spec.StorageClassName]; ok { - sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass - } - } - - newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts) - if err != nil { - errs.Add(namespace, err) - return warnings, errs - } - obj.Object = newObj - } - } - // Necessary because we may have remapped the namespace if the namespace is // blank, don't create the key. originalNamespace := obj.GetNamespace() From 82b01eba197acb8d3377365598aa7791e9efc17a Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Sun, 28 Nov 2021 17:55:36 +0800 Subject: [PATCH 3/7] Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373) Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/restore/restore.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 0529eb0fee..846d71c780 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -22,8 +22,6 @@ import ( "fmt" "io" "io/ioutil" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "path/filepath" "sort" "strings" @@ -1843,15 +1841,3 @@ func (ctx *restoreContext) getSelectedRestoreableItems(resource, targetNamespace } return restorable, warnings, errs } - -func getClient() (clientset *kubernetes.Clientset, err error) { - config, err := rest.InClusterConfig() - if err != nil { - return nil, err - } - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - return clientset, nil -} \ No newline at end of file From ce7caeb3e3e978cf67ca70c340b2c4ecbd383131 Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Sun, 28 Nov 2021 17:59:30 +0800 Subject: [PATCH 4/7] Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373) Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/restore/change_storageclass_action.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/restore/change_storageclass_action.go b/pkg/restore/change_storageclass_action.go index 6bed0809bb..db37432b03 100644 --- a/pkg/restore/change_storageclass_action.go +++ b/pkg/restore/change_storageclass_action.go @@ -99,7 +99,7 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut if len(sts.Spec.VolumeClaimTemplates) > 0 { for index, pvc := range sts.Spec.VolumeClaimTemplates { if newStorageClass, ok := config.Data[*pvc.Spec.StorageClassName]; ok { - log.Infof("Updating %s's storage class name to %s",sts.Name, newStorageClass) + log.Infof("Updating %s's storage class name to %s", sts.Name, newStorageClass) sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass } } From d76fc0594dc730d139134b6b7debe0a9e7010b90 Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Tue, 30 Nov 2021 14:47:19 +0800 Subject: [PATCH 5/7] Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373) Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/builder/statefulset_builder.go | 46 ++++++ pkg/builder/storage_class_builder.go | 29 +++- pkg/restore/change_storageclass_action.go | 53 ++++--- .../change_storageclass_action_test.go | 135 ++++++++++++++---- 4 files changed, 218 insertions(+), 45 deletions(-) create mode 100644 pkg/builder/statefulset_builder.go diff --git a/pkg/builder/statefulset_builder.go b/pkg/builder/statefulset_builder.go new file mode 100644 index 0000000000..6e0241ea5b --- /dev/null +++ b/pkg/builder/statefulset_builder.go @@ -0,0 +1,46 @@ +package builder + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// StatefulSetBuilder builds StatefulSet objects. +type StatefulSetBuilder struct { + object *appsv1.StatefulSet +} + +// ForStatefulSet is the constructor for a StatefulSetBuilder. +func ForStatefulSet(ns, name string) *StatefulSetBuilder { + return &StatefulSetBuilder{ + object: &appsv1.StatefulSet{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "StatefulSet", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + Spec: appsv1.StatefulSetSpec{ + VolumeClaimTemplates: []corev1.PersistentVolumeClaim{}, + }, + }, + } +} + +// Result returns the built StatefulSet. +func (b *StatefulSetBuilder) Result() *appsv1.StatefulSet { + return b.object +} + +// StorageClass sets the StatefulSet's VolumeClaimTemplates storage class name. +func (b *StatefulSetBuilder) StorageClass(names ...string) *StatefulSetBuilder { + for _, name := range names { + nameTmp := name + b.object.Spec.VolumeClaimTemplates = append(b.object.Spec.VolumeClaimTemplates, + corev1.PersistentVolumeClaim{Spec: corev1.PersistentVolumeClaimSpec{StorageClassName: &nameTmp}}) + } + return b +} diff --git a/pkg/builder/storage_class_builder.go b/pkg/builder/storage_class_builder.go index 9945433325..8ffe4afd11 100644 --- a/pkg/builder/storage_class_builder.go +++ b/pkg/builder/storage_class_builder.go @@ -23,7 +23,8 @@ import ( // StorageClassBuilder builds StorageClass objects. type StorageClassBuilder struct { - object *storagev1api.StorageClass + object *storagev1api.StorageClass + objectSlice []*storagev1api.StorageClass } // ForStorageClass is the constructor for a StorageClassBuilder. @@ -54,3 +55,29 @@ func (b *StorageClassBuilder) ObjectMeta(opts ...ObjectMetaOpt) *StorageClassBui return b } + +// ForStorageClassSlice is the constructor for a storageClassSlice in StorageClassBuilder. +func ForStorageClassSlice(names ...string) *StorageClassBuilder { + var storageClassSlice []*storagev1api.StorageClass + for _, name := range names { + storageClass := &storagev1api.StorageClass{ + TypeMeta: metav1.TypeMeta{ + APIVersion: storagev1api.SchemeGroupVersion.String(), + Kind: "StorageClass", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + storageClassSlice = append(storageClassSlice, storageClass) + } + + return &StorageClassBuilder{ + objectSlice: storageClassSlice, + } +} + +// SliceResult returns the built StorageClass slice. +func (b *StorageClassBuilder) SliceResult() []*storagev1api.StorageClass { + return b.objectSlice +} diff --git a/pkg/restore/change_storageclass_action.go b/pkg/restore/change_storageclass_action.go index db37432b03..90b8c42a55 100644 --- a/pkg/restore/change_storageclass_action.go +++ b/pkg/restore/change_storageclass_action.go @@ -18,10 +18,12 @@ package restore import ( "context" + "fmt" "github.com/pkg/errors" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -89,7 +91,7 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut "name": obj.GetName(), }) - // change sts volumeClaimTemplates storageClassName + // change StatefulSet volumeClaimTemplates storageClassName if obj.GetKind() == "StatefulSet" { sts := new(appsv1.StatefulSet) if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts); err != nil { @@ -98,15 +100,21 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut if len(sts.Spec.VolumeClaimTemplates) > 0 { for index, pvc := range sts.Spec.VolumeClaimTemplates { - if newStorageClass, ok := config.Data[*pvc.Spec.StorageClassName]; ok { - log.Infof("Updating %s's storage class name to %s", sts.Name, newStorageClass) - sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass + warn, err, newStorageClass := a.isStorageClassExist(*pvc.Spec.StorageClassName, config) + if warn != "" && err == nil { + log.Debug(warn) + continue + } else if warn == "" && err != nil { + return nil, err } + + log.Infof("Updating item's storage class name to %s", newStorageClass) + sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass } newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts) if err != nil { - return nil, errors.Wrap(err, "converts obj to sts failed") + return nil, errors.Wrap(err, "convert obj to StatefulSet failed") } obj.Object = newObj } @@ -117,20 +125,13 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut if err != nil { return nil, errors.Wrap(err, "error getting item's spec.storageClassName") } - if storageClass == "" { - log.Debug("Item has no storage class specified") - return velero.NewRestoreItemActionExecuteOutput(input.Item), nil - } - newStorageClass, ok := config.Data[storageClass] - if !ok { - log.Debugf("No mapping found for storage class %s", storageClass) + warn, err, newStorageClass := a.isStorageClassExist(storageClass, config) + if warn != "" && err == nil { + log.Debug(warn) return velero.NewRestoreItemActionExecuteOutput(input.Item), nil - } - - // validate that new storage class exists - if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil { - return nil, errors.Wrapf(err, "error getting storage class %s from API", newStorageClass) + } else if warn == "" && err != nil { + return nil, err } log.Infof("Updating item's storage class name to %s", newStorageClass) @@ -142,3 +143,21 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut return velero.NewRestoreItemActionExecuteOutput(obj), nil } + +func (a *ChangeStorageClassAction) isStorageClassExist(storageClass string, cm *corev1.ConfigMap) (warn string, err error, newStorageClass string) { + if storageClass == "" { + return "Item has no storage class specified", nil, "" + } + + newStorageClass, ok := cm.Data[storageClass] + if !ok { + return fmt.Sprintf("No mapping found for storage class %s", storageClass), nil, "" + } + + // validate that new storage class exists + if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil { + return "", errors.Wrapf(err, "error getting storage class %s from API", newStorageClass), newStorageClass + } + + return "", nil, newStorageClass +} diff --git a/pkg/restore/change_storageclass_action_test.go b/pkg/restore/change_storageclass_action_test.go index 9e6005851d..65de052db2 100644 --- a/pkg/restore/change_storageclass_action_test.go +++ b/pkg/restore/change_storageclass_action_test.go @@ -41,16 +41,17 @@ import ( // desired result. func TestChangeStorageClassActionExecute(t *testing.T) { tests := []struct { - name string - pvOrPVC interface{} - configMap *corev1api.ConfigMap - storageClass *storagev1api.StorageClass - want interface{} - wantErr error + name string + pvOrPvcOrSTS interface{} + configMap *corev1api.ConfigMap + storageClass *storagev1api.StorageClass + storageClassSlice []*storagev1api.StorageClass + want interface{} + wantErr error }{ { - name: "a valid mapping for a persistent volume is applied correctly", - pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), + name: "a valid mapping for a persistent volume is applied correctly", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "storageclass-2"). @@ -59,8 +60,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-2").Result(), }, { - name: "a valid mapping for a persistent volume claim is applied correctly", - pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), + name: "a valid mapping for a persistent volume claim is applied correctly", + pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "storageclass-2"). @@ -69,8 +70,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-2").Result(), }, { - name: "when no config map exists for the plugin, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), + name: "when no config map exists for the plugin, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/some-other-plugin", "RestoreItemAction")). Data("storageclass-1", "storageclass-2"). @@ -78,16 +79,16 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), }, { - name: "when no storage class mappings exist in the plugin config map, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), + name: "when no storage class mappings exist in the plugin config map, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Result(), want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), }, { - name: "when persistent volume has no storage class, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolume("pv-1").Result(), + name: "when persistent volume has no storage class, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "storageclass-2"). @@ -95,8 +96,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolume("pv-1").Result(), }, { - name: "when persistent volume claim has no storage class, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(), + name: "when persistent volume claim has no storage class, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "storageclass-2"). @@ -104,8 +105,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(), }, { - name: "when persistent volume's storage class has no mapping in the config map, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), + name: "when persistent volume's storage class has no mapping in the config map, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-3", "storageclass-4"). @@ -113,8 +114,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), }, { - name: "when persistent volume claim's storage class has no mapping in the config map, the item is returned as-is", - pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), + name: "when persistent volume claim's storage class has no mapping in the config map, the item is returned as-is", + pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-3", "storageclass-4"). @@ -122,8 +123,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) { want: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), }, { - name: "when persistent volume's storage class is mapped to a nonexistent storage class, an error is returned", - pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), + name: "when persistent volume's storage class is mapped to a nonexistent storage class, an error is returned", + pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "nonexistent-storage-class"). @@ -131,8 +132,81 @@ func TestChangeStorageClassActionExecute(t *testing.T) { wantErr: errors.New("error getting storage class nonexistent-storage-class from API: storageclasses.storage.k8s.io \"nonexistent-storage-class\" not found"), }, { - name: "when persistent volume claim's storage class is mapped to a nonexistent storage class, an error is returned", - pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), + name: "when persistent volume claim's storage class is mapped to a nonexistent storage class, an error is returned", + pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Data("storageclass-1", "nonexistent-storage-class"). + Result(), + wantErr: errors.New("error getting storage class nonexistent-storage-class from API: storageclasses.storage.k8s.io \"nonexistent-storage-class\" not found"), + }, + { + name: "when statefulset's VolumeClaimTemplates has only one pvc, a valid mapping for a statefulset is applied correctly", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Data("storageclass-1", "storageclass-2"). + Result(), + storageClass: builder.ForStorageClass("storageclass-2").Result(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-2").Result(), + }, + { + name: "when statefulset's VolumeClaimTemplates has more than one same pvc's storageClassName, a valid mapping for a statefulset is applied correctly", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1", "storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Data("storageclass-1", "storageclass-2", "storageclass-3", "storageclass-4"). + Result(), + storageClass: builder.ForStorageClass("storageclass-2").Result(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-2", "storageclass-2").Result(), + }, + { + name: "when statefulset's VolumeClaimTemplates has more than one different pvc's storageClassName, a valid mapping for a statefulset is applied correctly", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1", "storageclass-2", "storageclass-3").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Data("storageclass-1", "storageclass-a", "storageclass-2", "storageclass-b", "storageclass-3", "storageclass-c"). + Result(), + storageClassSlice: builder.ForStorageClassSlice("storageclass-a", "storageclass-b", "storageclass-c").SliceResult(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-a", "storageclass-b", "storageclass-c").Result(), + }, + { + name: "when no config map exists for the plugin, the statefulset item is returned as-is", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/some-other-plugin", "RestoreItemAction")). + Data("storageclass-1", "storageclass-2"). + Result(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + }, + { + name: "when no storage class mappings exist in the plugin config map, the statefulset item is returned as-is", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Result(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + }, + { + name: "when persistent volume claim has no storage class, the statefulset item is returned as-is", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Result(), + want: builder.ForStatefulSet("velero", "sts-1").Result(), + }, + { + name: "when statefulset's storage class has no mapping in the config map, the item is returned as-is", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + configMap: builder.ForConfigMap("velero", "change-storage-classs"). + ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). + Data("storageclass-3", "storageclass-4"). + Result(), + want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), + }, + { + name: "when statefulset's storage class is mapped to a nonexistent storage class, an error is returned", + pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(), configMap: builder.ForConfigMap("velero", "change-storage-classs"). ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")). Data("storageclass-1", "nonexistent-storage-class"). @@ -161,7 +235,14 @@ func TestChangeStorageClassActionExecute(t *testing.T) { require.NoError(t, err) } - unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvOrPVC) + if tc.storageClassSlice != nil { + for _, storageClass := range tc.storageClassSlice { + _, err := clientset.StorageV1().StorageClasses().Create(context.TODO(), storageClass, metav1.CreateOptions{}) + require.NoError(t, err) + } + } + + unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvOrPvcOrSTS) require.NoError(t, err) input := &velero.RestoreItemActionExecuteInput{ From 1685f5d6fa3b627024992fe7d9b475eea916beb5 Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Thu, 2 Dec 2021 15:54:49 +0800 Subject: [PATCH 6/7] Change the isStorageClassExist logic Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/builder/statefulset_builder.go | 16 +++++++++++ pkg/restore/change_storageclass_action.go | 33 +++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/pkg/builder/statefulset_builder.go b/pkg/builder/statefulset_builder.go index 6e0241ea5b..0edcd0455e 100644 --- a/pkg/builder/statefulset_builder.go +++ b/pkg/builder/statefulset_builder.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 the Velero contributors. + +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 builder import ( diff --git a/pkg/restore/change_storageclass_action.go b/pkg/restore/change_storageclass_action.go index 90b8c42a55..728cb6248a 100644 --- a/pkg/restore/change_storageclass_action.go +++ b/pkg/restore/change_storageclass_action.go @@ -18,8 +18,6 @@ package restore import ( "context" - "fmt" - "github.com/pkg/errors" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" @@ -100,12 +98,11 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut if len(sts.Spec.VolumeClaimTemplates) > 0 { for index, pvc := range sts.Spec.VolumeClaimTemplates { - warn, err, newStorageClass := a.isStorageClassExist(*pvc.Spec.StorageClassName, config) - if warn != "" && err == nil { - log.Debug(warn) - continue - } else if warn == "" && err != nil { + exists, newStorageClass, err := a.isStorageClassExist(log, *pvc.Spec.StorageClassName, config) + if err != nil { return nil, err + } else if !exists { + continue } log.Infof("Updating item's storage class name to %s", newStorageClass) @@ -126,12 +123,11 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut return nil, errors.Wrap(err, "error getting item's spec.storageClassName") } - warn, err, newStorageClass := a.isStorageClassExist(storageClass, config) - if warn != "" && err == nil { - log.Debug(warn) - return velero.NewRestoreItemActionExecuteOutput(input.Item), nil - } else if warn == "" && err != nil { + exists, newStorageClass, err := a.isStorageClassExist(log, storageClass, config) + if err != nil { return nil, err + } else if !exists { + return velero.NewRestoreItemActionExecuteOutput(input.Item), nil } log.Infof("Updating item's storage class name to %s", newStorageClass) @@ -140,24 +136,25 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut return nil, errors.Wrap(err, "unable to set item's spec.storageClassName") } } - return velero.NewRestoreItemActionExecuteOutput(obj), nil } -func (a *ChangeStorageClassAction) isStorageClassExist(storageClass string, cm *corev1.ConfigMap) (warn string, err error, newStorageClass string) { +func (a *ChangeStorageClassAction) isStorageClassExist(log *logrus.Entry, storageClass string, cm *corev1.ConfigMap) (exists bool, newStorageClass string, err error) { if storageClass == "" { - return "Item has no storage class specified", nil, "" + log.Debug("Item has no storage class specified") + return false, "", nil } newStorageClass, ok := cm.Data[storageClass] if !ok { - return fmt.Sprintf("No mapping found for storage class %s", storageClass), nil, "" + log.Debugf("No mapping found for storage class %s", storageClass) + return false, "", nil } // validate that new storage class exists if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil { - return "", errors.Wrapf(err, "error getting storage class %s from API", newStorageClass), newStorageClass + return false, "", errors.Wrapf(err, "error getting storage class %s from API", newStorageClass) } - return "", nil, newStorageClass + return true, newStorageClass, nil } From 8b543d360b69f854b70d4e6dfa7e23eafb553b48 Mon Sep 17 00:00:00 2001 From: Box-Cube <64300761+Box-Cube@users.noreply.github.com> Date: Wed, 15 Dec 2021 09:06:26 +0800 Subject: [PATCH 7/7] Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373) Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com> --- pkg/restore/change_storageclass_action.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/restore/change_storageclass_action.go b/pkg/restore/change_storageclass_action.go index 728cb6248a..5714a1a7f3 100644 --- a/pkg/restore/change_storageclass_action.go +++ b/pkg/restore/change_storageclass_action.go @@ -18,6 +18,7 @@ package restore import ( "context" + "github.com/pkg/errors" "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1"