Fix statefulsets volumeClaimTemplates storageClassName after use Changing PV/PVC Storage Classes (#4375)

* fix statefulsets volumeClaimTemplates storageClassName after use Changing PV/PVC Storage Classes

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Fix (vmware-tanzu#4373)

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373)

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373)

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373)

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Change the isStorageClassExist logic

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>

* Fix StatefulSet volumeClaimTemplates storageClassName(vmware-tanzu#4373)

Signed-off-by: Box-Cube <64300761+Box-Cube@users.noreply.github.com>
This commit is contained in:
Box-Cube
2021-12-16 14:50:56 +08:00
committed by GitHub
parent e350ce5bb4
commit 69f6c8d0cd
5 changed files with 258 additions and 45 deletions
+59 -17
View File
@@ -21,8 +21,11 @@ import (
"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"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
storagev1client "k8s.io/client-go/kubernetes/typed/storage/v1"
@@ -55,7 +58,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,33 +90,72 @@ 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")
// change StatefulSet 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 {
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)
sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass
}
newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts)
if err != nil {
return nil, errors.Wrap(err, "convert obj to StatefulSet 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")
}
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)
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
}
func (a *ChangeStorageClassAction) isStorageClassExist(log *logrus.Entry, storageClass string, cm *corev1.ConfigMap) (exists bool, newStorageClass string, err error) {
if storageClass == "" {
log.Debug("Item has no storage class specified")
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
return false, "", nil
}
newStorageClass, ok := config.Data[storageClass]
newStorageClass, ok := cm.Data[storageClass]
if !ok {
log.Debugf("No mapping found for storage class %s", storageClass)
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
return false, "", 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)
return false, "", 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
return true, newStorageClass, nil
}
+108 -27
View File
@@ -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{