k8s.io/kubernetes@v1.29.3/pkg/api/persistentvolumeclaim/util_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package persistentvolumeclaim
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"k8s.io/apimachinery/pkg/api/resource"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    29  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    30  	"k8s.io/utils/ptr"
    31  
    32  	"k8s.io/kubernetes/pkg/apis/core"
    33  	"k8s.io/kubernetes/pkg/features"
    34  )
    35  
    36  func TestDropDisabledSnapshotDataSource(t *testing.T) {
    37  	pvcWithoutDataSource := func() *core.PersistentVolumeClaim {
    38  		return &core.PersistentVolumeClaim{
    39  			Spec: core.PersistentVolumeClaimSpec{
    40  				DataSource: nil,
    41  			},
    42  		}
    43  	}
    44  	apiGroup := "snapshot.storage.k8s.io"
    45  	pvcWithDataSource := func() *core.PersistentVolumeClaim {
    46  		return &core.PersistentVolumeClaim{
    47  			Spec: core.PersistentVolumeClaimSpec{
    48  				DataSource: &core.TypedLocalObjectReference{
    49  					APIGroup: &apiGroup,
    50  					Kind:     "VolumeSnapshot",
    51  					Name:     "test_snapshot",
    52  				},
    53  			},
    54  		}
    55  	}
    56  
    57  	pvcInfo := []struct {
    58  		description string
    59  		pvc         func() *core.PersistentVolumeClaim
    60  	}{
    61  		{
    62  			description: "pvc without DataSource",
    63  			pvc:         pvcWithoutDataSource,
    64  		},
    65  		{
    66  			description: "pvc with DataSource",
    67  			pvc:         pvcWithDataSource,
    68  		},
    69  		{
    70  			description: "is nil",
    71  			pvc:         func() *core.PersistentVolumeClaim { return nil },
    72  		},
    73  	}
    74  
    75  	for _, oldpvcInfo := range pvcInfo {
    76  		for _, newpvcInfo := range pvcInfo {
    77  			oldpvc := oldpvcInfo.pvc()
    78  			newpvc := newpvcInfo.pvc()
    79  			if newpvc == nil {
    80  				continue
    81  			}
    82  
    83  			t.Run(fmt.Sprintf("old pvc %v, new pvc %v", oldpvcInfo.description, newpvcInfo.description), func(t *testing.T) {
    84  				EnforceDataSourceBackwardsCompatibility(&newpvc.Spec, nil)
    85  
    86  				// old pvc should never be changed
    87  				if !reflect.DeepEqual(oldpvc, oldpvcInfo.pvc()) {
    88  					t.Errorf("old pvc changed: %v", cmp.Diff(oldpvc, oldpvcInfo.pvc()))
    89  				}
    90  
    91  				// new pvc should not be changed
    92  				if !reflect.DeepEqual(newpvc, newpvcInfo.pvc()) {
    93  					t.Errorf("new pvc changed: %v", cmp.Diff(newpvc, newpvcInfo.pvc()))
    94  				}
    95  			})
    96  		}
    97  	}
    98  }
    99  
   100  // TestPVCDataSourceSpecFilter checks to ensure the DropDisabledFields function behaves correctly for PVCDataSource featuregate
   101  func TestPVCDataSourceSpecFilter(t *testing.T) {
   102  	apiGroup := ""
   103  	validSpec := core.PersistentVolumeClaimSpec{
   104  		DataSource: &core.TypedLocalObjectReference{
   105  			APIGroup: &apiGroup,
   106  			Kind:     "PersistentVolumeClaim",
   107  			Name:     "test_clone",
   108  		},
   109  	}
   110  	validSpecNilAPIGroup := core.PersistentVolumeClaimSpec{
   111  		DataSource: &core.TypedLocalObjectReference{
   112  			Kind: "PersistentVolumeClaim",
   113  			Name: "test_clone",
   114  		},
   115  	}
   116  
   117  	invalidAPIGroup := "invalid.pvc.api.group"
   118  	invalidSpec := core.PersistentVolumeClaimSpec{
   119  		DataSource: &core.TypedLocalObjectReference{
   120  			APIGroup: &invalidAPIGroup,
   121  			Kind:     "PersistentVolumeClaim",
   122  			Name:     "test_clone_invalid",
   123  		},
   124  	}
   125  
   126  	var tests = map[string]struct {
   127  		spec core.PersistentVolumeClaimSpec
   128  		want *core.TypedLocalObjectReference
   129  	}{
   130  		"enabled with empty ds": {
   131  			spec: core.PersistentVolumeClaimSpec{},
   132  			want: nil,
   133  		},
   134  		"enabled with invalid spec": {
   135  			spec: invalidSpec,
   136  			want: nil,
   137  		},
   138  		"enabled with valid spec": {
   139  			spec: validSpec,
   140  			want: validSpec.DataSource,
   141  		},
   142  		"enabled with valid spec but nil APIGroup": {
   143  			spec: validSpecNilAPIGroup,
   144  			want: validSpecNilAPIGroup.DataSource,
   145  		},
   146  	}
   147  
   148  	for testName, test := range tests {
   149  		t.Run(testName, func(t *testing.T) {
   150  			EnforceDataSourceBackwardsCompatibility(&test.spec, nil)
   151  			if test.spec.DataSource != test.want {
   152  				t.Errorf("expected drop datasource condition was not met, test: %s, spec: %v, expected: %v", testName, test.spec, test.want)
   153  			}
   154  
   155  		})
   156  	}
   157  }
   158  
   159  var (
   160  	coreGroup    = ""
   161  	snapGroup    = "snapshot.storage.k8s.io"
   162  	genericGroup = "generic.storage.k8s.io"
   163  	pvcKind      = "PersistentVolumeClaim"
   164  	snapKind     = "VolumeSnapshot"
   165  	genericKind  = "Generic"
   166  	podKind      = "Pod"
   167  )
   168  
   169  func makeDataSource(apiGroup, kind, name string) *core.TypedLocalObjectReference {
   170  	return &core.TypedLocalObjectReference{
   171  		APIGroup: &apiGroup,
   172  		Kind:     kind,
   173  		Name:     name,
   174  	}
   175  }
   176  
   177  func makeDataSourceRef(apiGroup, kind, name string, namespace *string) *core.TypedObjectReference {
   178  	return &core.TypedObjectReference{
   179  		APIGroup:  &apiGroup,
   180  		Kind:      kind,
   181  		Name:      name,
   182  		Namespace: namespace,
   183  	}
   184  }
   185  
   186  // TestDataSourceFilter checks to ensure the AnyVolumeDataSource feature gate and CrossNamespaceVolumeDataSource works
   187  func TestDataSourceFilter(t *testing.T) {
   188  	ns := "ns1"
   189  	volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol")
   190  	volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil)
   191  	xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns)
   192  
   193  	var tests = map[string]struct {
   194  		spec       core.PersistentVolumeClaimSpec
   195  		oldSpec    core.PersistentVolumeClaimSpec
   196  		anyEnabled bool
   197  		xnsEnabled bool
   198  		want       *core.TypedLocalObjectReference
   199  		wantRef    *core.TypedObjectReference
   200  	}{
   201  		"any disabled with empty ds": {
   202  			spec: core.PersistentVolumeClaimSpec{},
   203  		},
   204  		"any disabled with volume ds": {
   205  			spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
   206  			want: volumeDataSource,
   207  		},
   208  		"any disabled with volume ds ref": {
   209  			spec: core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef},
   210  		},
   211  		"any disabled with both data sources": {
   212  			spec: core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef},
   213  			want: volumeDataSource,
   214  		},
   215  		"any enabled with empty ds": {
   216  			spec:       core.PersistentVolumeClaimSpec{},
   217  			anyEnabled: true,
   218  		},
   219  		"any enabled with volume ds": {
   220  			spec:       core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
   221  			anyEnabled: true,
   222  			want:       volumeDataSource,
   223  		},
   224  		"any enabled with volume ds ref": {
   225  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef},
   226  			anyEnabled: true,
   227  			wantRef:    volumeDataSourceRef,
   228  		},
   229  		"any enabled with both data sources": {
   230  			spec:       core.PersistentVolumeClaimSpec{DataSource: volumeDataSource, DataSourceRef: volumeDataSourceRef},
   231  			anyEnabled: true,
   232  			want:       volumeDataSource,
   233  			wantRef:    volumeDataSourceRef,
   234  		},
   235  		"both any and xns enabled with xns volume ds": {
   236  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   237  			anyEnabled: true,
   238  			xnsEnabled: true,
   239  			wantRef:    xnsVolumeDataSourceRef,
   240  		},
   241  		"both any and xns enabled with xns volume ds when xns volume exists in oldSpec": {
   242  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   243  			oldSpec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   244  			anyEnabled: true,
   245  			xnsEnabled: true,
   246  			wantRef:    xnsVolumeDataSourceRef,
   247  		},
   248  		"only xns enabled with xns volume ds": {
   249  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   250  			xnsEnabled: true,
   251  		},
   252  		"only any enabled with xns volume ds": {
   253  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   254  			anyEnabled: true,
   255  		},
   256  		"only any enabled with xns volume ds when xns volume exists in oldSpec": {
   257  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   258  			oldSpec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   259  			anyEnabled: true,
   260  			wantRef:    xnsVolumeDataSourceRef, // existing field isn't dropped.
   261  		},
   262  		"only any enabled with xns volume ds when volume exists in oldSpec": {
   263  			spec:       core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   264  			oldSpec:    core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef},
   265  			anyEnabled: true,
   266  			wantRef:    xnsVolumeDataSourceRef, // existing field isn't dropped.8
   267  		},
   268  	}
   269  
   270  	for testName, test := range tests {
   271  		t.Run(testName, func(t *testing.T) {
   272  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, test.anyEnabled)()
   273  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, test.xnsEnabled)()
   274  			DropDisabledFields(&test.spec, &test.oldSpec)
   275  			if test.spec.DataSource != test.want {
   276  				t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSource: %+v",
   277  					testName, test.anyEnabled, test.xnsEnabled, test.spec, test.want)
   278  			}
   279  			if test.spec.DataSourceRef != test.wantRef {
   280  				t.Errorf("expected condition was not met, test: %s, anyEnabled: %v, xnsEnabled: %v, spec: %+v, expected DataSourceRef: %+v",
   281  					testName, test.anyEnabled, test.xnsEnabled, test.spec, test.wantRef)
   282  			}
   283  		})
   284  	}
   285  }
   286  
   287  // TestDataSourceRef checks to ensure the DataSourceRef field handles backwards
   288  // compatibility with the DataSource field
   289  func TestDataSourceRef(t *testing.T) {
   290  	ns := "ns1"
   291  	volumeDataSource := makeDataSource(coreGroup, pvcKind, "my-vol")
   292  	volumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", nil)
   293  	xnsVolumeDataSourceRef := makeDataSourceRef(coreGroup, pvcKind, "my-vol", &ns)
   294  	snapshotDataSource := makeDataSource(snapGroup, snapKind, "my-snap")
   295  	snapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", nil)
   296  	xnsSnapshotDataSourceRef := makeDataSourceRef(snapGroup, snapKind, "my-snap", &ns)
   297  	genericDataSource := makeDataSource(genericGroup, genericKind, "my-foo")
   298  	genericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", nil)
   299  	xnsGenericDataSourceRef := makeDataSourceRef(genericGroup, genericKind, "my-foo", &ns)
   300  	coreDataSource := makeDataSource(coreGroup, podKind, "my-pod")
   301  	coreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", nil)
   302  	xnsCoreDataSourceRef := makeDataSourceRef(coreGroup, podKind, "my-pod", &ns)
   303  
   304  	var tests = map[string]struct {
   305  		spec    core.PersistentVolumeClaimSpec
   306  		want    *core.TypedLocalObjectReference
   307  		wantRef *core.TypedObjectReference
   308  	}{
   309  		"empty ds": {
   310  			spec: core.PersistentVolumeClaimSpec{},
   311  		},
   312  		"volume ds": {
   313  			spec:    core.PersistentVolumeClaimSpec{DataSource: volumeDataSource},
   314  			want:    volumeDataSource,
   315  			wantRef: volumeDataSourceRef,
   316  		},
   317  		"snapshot ds": {
   318  			spec:    core.PersistentVolumeClaimSpec{DataSource: snapshotDataSource},
   319  			want:    snapshotDataSource,
   320  			wantRef: snapshotDataSourceRef,
   321  		},
   322  		"generic ds": {
   323  			spec:    core.PersistentVolumeClaimSpec{DataSource: genericDataSource},
   324  			want:    genericDataSource,
   325  			wantRef: genericDataSourceRef,
   326  		},
   327  		"core ds": {
   328  			spec:    core.PersistentVolumeClaimSpec{DataSource: coreDataSource},
   329  			want:    coreDataSource,
   330  			wantRef: coreDataSourceRef,
   331  		},
   332  		"volume ds ref": {
   333  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: volumeDataSourceRef},
   334  			want:    volumeDataSource,
   335  			wantRef: volumeDataSourceRef,
   336  		},
   337  		"snapshot ds ref": {
   338  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: snapshotDataSourceRef},
   339  			want:    snapshotDataSource,
   340  			wantRef: snapshotDataSourceRef,
   341  		},
   342  		"generic ds ref": {
   343  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: genericDataSourceRef},
   344  			want:    genericDataSource,
   345  			wantRef: genericDataSourceRef,
   346  		},
   347  		"core ds ref": {
   348  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: coreDataSourceRef},
   349  			want:    coreDataSource,
   350  			wantRef: coreDataSourceRef,
   351  		},
   352  		"xns volume ds ref": {
   353  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsVolumeDataSourceRef},
   354  			wantRef: xnsVolumeDataSourceRef,
   355  		},
   356  		"xns snapshot ds ref": {
   357  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsSnapshotDataSourceRef},
   358  			wantRef: xnsSnapshotDataSourceRef,
   359  		},
   360  		"xns generic ds ref": {
   361  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsGenericDataSourceRef},
   362  			wantRef: xnsGenericDataSourceRef,
   363  		},
   364  		"xns core ds ref": {
   365  			spec:    core.PersistentVolumeClaimSpec{DataSourceRef: xnsCoreDataSourceRef},
   366  			wantRef: xnsCoreDataSourceRef,
   367  		},
   368  	}
   369  
   370  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AnyVolumeDataSource, true)()
   371  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CrossNamespaceVolumeDataSource, true)()
   372  
   373  	for testName, test := range tests {
   374  		t.Run(testName, func(t *testing.T) {
   375  			NormalizeDataSources(&test.spec)
   376  			if !reflect.DeepEqual(test.spec.DataSource, test.want) {
   377  				t.Errorf("expected condition was not met, test: %s, spec.datasource: %+v, want: %+v",
   378  					testName, test.spec.DataSource, test.want)
   379  			}
   380  			if !reflect.DeepEqual(test.spec.DataSourceRef, test.wantRef) {
   381  				t.Errorf("expected condition was not met, test: %s, spec.datasourceRef: %+v, wantRef: %+v",
   382  					testName, test.spec.DataSourceRef, test.wantRef)
   383  			}
   384  		})
   385  	}
   386  }
   387  
   388  func TestDropDisabledVolumeAttributesClass(t *testing.T) {
   389  	vacName := ptr.To("foo")
   390  
   391  	var tests = map[string]struct {
   392  		spec       core.PersistentVolumeClaimSpec
   393  		oldSpec    core.PersistentVolumeClaimSpec
   394  		vacEnabled bool
   395  		wantVAC    *string
   396  	}{
   397  		"vac disabled with empty vac": {
   398  			spec: core.PersistentVolumeClaimSpec{},
   399  		},
   400  		"vac disabled with vac": {
   401  			spec: core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   402  		},
   403  		"vac enabled with empty vac": {
   404  			spec:       core.PersistentVolumeClaimSpec{},
   405  			vacEnabled: true,
   406  		},
   407  		"vac enabled with vac": {
   408  			spec:       core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   409  			vacEnabled: true,
   410  			wantVAC:    vacName,
   411  		},
   412  		"vac disabled with vac when vac doesn't exists in oldSpec": {
   413  			spec:    core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   414  			oldSpec: core.PersistentVolumeClaimSpec{},
   415  		},
   416  		"vac disabled with vac when vac exists in oldSpec": {
   417  			spec:       core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   418  			oldSpec:    core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   419  			vacEnabled: false,
   420  			wantVAC:    vacName,
   421  		},
   422  		"vac enabled with vac when vac doesn't exists in oldSpec": {
   423  			spec:       core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   424  			oldSpec:    core.PersistentVolumeClaimSpec{},
   425  			vacEnabled: true,
   426  			wantVAC:    vacName,
   427  		},
   428  		"vac enable with vac when vac exists in oldSpec": {
   429  			spec:       core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   430  			oldSpec:    core.PersistentVolumeClaimSpec{VolumeAttributesClassName: vacName},
   431  			vacEnabled: true,
   432  			wantVAC:    vacName,
   433  		},
   434  	}
   435  
   436  	for testName, test := range tests {
   437  		t.Run(testName, func(t *testing.T) {
   438  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.vacEnabled)()
   439  			DropDisabledFields(&test.spec, &test.oldSpec)
   440  			if test.spec.VolumeAttributesClassName != test.wantVAC {
   441  				t.Errorf("expected vac was not met, test: %s, vacEnabled: %v, spec: %+v, expected VAC: %+v",
   442  					testName, test.vacEnabled, test.spec, test.wantVAC)
   443  			}
   444  		})
   445  	}
   446  }
   447  
   448  func TestDropDisabledFieldsFromStatus(t *testing.T) {
   449  	tests := []struct {
   450  		name                                string
   451  		enableRecoverVolumeExpansionFailure bool
   452  		enableVolumeAttributesClass         bool
   453  		pvc                                 *core.PersistentVolumeClaim
   454  		oldPVC                              *core.PersistentVolumeClaim
   455  		expected                            *core.PersistentVolumeClaim
   456  	}{
   457  		{
   458  			name:                                "for:newPVC=hasAllocatedResource,oldPVC=doesnot,featuregate=false; should drop field",
   459  			enableRecoverVolumeExpansionFailure: false,
   460  			enableVolumeAttributesClass:         false,
   461  			pvc:                                 withAllocatedResource("5G"),
   462  			oldPVC:                              getPVC(),
   463  			expected:                            getPVC(),
   464  		},
   465  		{
   466  			name:                                "for:newPVC=hasAllocatedResource,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
   467  			enableRecoverVolumeExpansionFailure: true,
   468  			enableVolumeAttributesClass:         false,
   469  			pvc:                                 withAllocatedResource("5G"),
   470  			oldPVC:                              getPVC(),
   471  			expected:                            withAllocatedResource("5G"),
   472  		},
   473  		{
   474  			name:                                "for:newPVC=hasAllocatedResource,oldPVC=hasAllocatedResource,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
   475  			enableRecoverVolumeExpansionFailure: true,
   476  			enableVolumeAttributesClass:         false,
   477  			pvc:                                 withAllocatedResource("5G"),
   478  			oldPVC:                              withAllocatedResource("5G"),
   479  			expected:                            withAllocatedResource("5G"),
   480  		},
   481  		{
   482  			name:                                "for:newPVC=hasAllocatedResource,oldPVC=hasAllocatedResource,featuregate=false; should keep field",
   483  			enableRecoverVolumeExpansionFailure: false,
   484  			enableVolumeAttributesClass:         false,
   485  			pvc:                                 withAllocatedResource("10G"),
   486  			oldPVC:                              withAllocatedResource("5G"),
   487  			expected:                            withAllocatedResource("10G"),
   488  		},
   489  		{
   490  			name:                                "for:newPVC=hasAllocatedResource,oldPVC=nil,featuregate=false; should drop field",
   491  			enableRecoverVolumeExpansionFailure: false,
   492  			enableVolumeAttributesClass:         false,
   493  			pvc:                                 withAllocatedResource("5G"),
   494  			oldPVC:                              nil,
   495  			expected:                            getPVC(),
   496  		},
   497  		{
   498  			name:                                "for:newPVC=hasResizeStatus,oldPVC=nil, featuregate=false should drop field",
   499  			enableRecoverVolumeExpansionFailure: false,
   500  			enableVolumeAttributesClass:         false,
   501  			pvc:                                 withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   502  			oldPVC:                              nil,
   503  			expected:                            getPVC(),
   504  		},
   505  		{
   506  			name:                                "for:newPVC=hasResizeStatus,oldPVC=doesnot,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
   507  			enableRecoverVolumeExpansionFailure: true,
   508  			enableVolumeAttributesClass:         false,
   509  			pvc:                                 withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   510  			oldPVC:                              getPVC(),
   511  			expected:                            withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   512  		},
   513  		{
   514  			name:                                "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=RecoverVolumeExpansionFailure=true; should keep field",
   515  			enableRecoverVolumeExpansionFailure: true,
   516  			enableVolumeAttributesClass:         false,
   517  			pvc:                                 withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   518  			oldPVC:                              withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   519  			expected:                            withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   520  		},
   521  		{
   522  			name:                                "for:newPVC=hasResizeStatus,oldPVC=hasResizeStatus,featuregate=false; should keep field",
   523  			enableRecoverVolumeExpansionFailure: false,
   524  			enableVolumeAttributesClass:         false,
   525  			pvc:                                 withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   526  			oldPVC:                              withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   527  			expected:                            withResizeStatus(core.PersistentVolumeClaimNodeResizeFailed),
   528  		},
   529  		{
   530  			name:                                "for:newPVC=hasVolumeAttributeClass,oldPVC=nil, featuregate=false should drop field",
   531  			enableRecoverVolumeExpansionFailure: false,
   532  			enableVolumeAttributesClass:         false,
   533  			pvc:                                 withVolumeAttributesClassName("foo"),
   534  			oldPVC:                              nil,
   535  			expected:                            getPVC(),
   536  		},
   537  		{
   538  			name:                                "for:newPVC=hasVolumeAttributeClass,oldPVC=doesnot,featuregate=VolumeAttributesClass=true; should keep field",
   539  			enableRecoverVolumeExpansionFailure: false,
   540  			enableVolumeAttributesClass:         true,
   541  			pvc:                                 withVolumeAttributesClassName("foo"),
   542  			oldPVC:                              getPVC(),
   543  			expected:                            withVolumeAttributesClassName("foo"),
   544  		},
   545  		{
   546  			name:                                "for:newPVC=hasVolumeAttributeClass,oldPVC=hasVolumeAttributeClass,featuregate=VolumeAttributesClass=true; should keep field",
   547  			enableRecoverVolumeExpansionFailure: false,
   548  			enableVolumeAttributesClass:         true,
   549  			pvc:                                 withVolumeAttributesClassName("foo"),
   550  			oldPVC:                              withVolumeAttributesClassName("foo"),
   551  			expected:                            withVolumeAttributesClassName("foo"),
   552  		},
   553  		{
   554  			name:                                "for:newPVC=hasVolumeAttributeClass,oldPVC=hasVolumeAttributeClass,featuregate=false; should keep field",
   555  			enableRecoverVolumeExpansionFailure: false,
   556  			enableVolumeAttributesClass:         false,
   557  			pvc:                                 withVolumeAttributesClassName("foo"),
   558  			oldPVC:                              withVolumeAttributesClassName("foo"),
   559  			expected:                            withVolumeAttributesClassName("foo"),
   560  		},
   561  		{
   562  			name:                                "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=nil, featuregate=false should drop field",
   563  			enableRecoverVolumeExpansionFailure: false,
   564  			enableVolumeAttributesClass:         false,
   565  			pvc:                                 withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   566  			oldPVC:                              nil,
   567  			expected:                            getPVC(),
   568  		},
   569  		{
   570  			name:                                "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=doesnot,featuregate=VolumeAttributesClass=true; should keep field",
   571  			enableRecoverVolumeExpansionFailure: false,
   572  			enableVolumeAttributesClass:         true,
   573  			pvc:                                 withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   574  			oldPVC:                              getPVC(),
   575  			expected:                            withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   576  		},
   577  		{
   578  			name:                                "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=hasVolumeAttributesModifyStatus,featuregate=VolumeAttributesClass=true; should keep field",
   579  			enableRecoverVolumeExpansionFailure: false,
   580  			enableVolumeAttributesClass:         true,
   581  			pvc:                                 withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   582  			oldPVC:                              withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   583  			expected:                            withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   584  		},
   585  		{
   586  			name:                                "for:newPVC=hasVolumeAttributesModifyStatus,oldPVC=hasVolumeAttributesModifyStatus,featuregate=false; should keep field",
   587  			enableRecoverVolumeExpansionFailure: false,
   588  			enableVolumeAttributesClass:         false,
   589  			pvc:                                 withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   590  			oldPVC:                              withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   591  			expected:                            withVolumeAttributesModifyStatus("bar", core.PersistentVolumeClaimModifyVolumePending),
   592  		},
   593  	}
   594  
   595  	for _, test := range tests {
   596  		t.Run(test.name, func(t *testing.T) {
   597  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, test.enableRecoverVolumeExpansionFailure)()
   598  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, test.enableVolumeAttributesClass)()
   599  
   600  			DropDisabledFieldsFromStatus(test.pvc, test.oldPVC)
   601  
   602  			if !reflect.DeepEqual(*test.expected, *test.pvc) {
   603  				t.Errorf("Unexpected change: %+v", cmp.Diff(test.expected, test.pvc))
   604  			}
   605  		})
   606  	}
   607  }
   608  
   609  func getPVC() *core.PersistentVolumeClaim {
   610  	return &core.PersistentVolumeClaim{}
   611  }
   612  
   613  func withAllocatedResource(q string) *core.PersistentVolumeClaim {
   614  	return &core.PersistentVolumeClaim{
   615  		Status: core.PersistentVolumeClaimStatus{
   616  			AllocatedResources: core.ResourceList{
   617  				core.ResourceStorage: resource.MustParse(q),
   618  			},
   619  		},
   620  	}
   621  }
   622  
   623  func withResizeStatus(status core.ClaimResourceStatus) *core.PersistentVolumeClaim {
   624  	return &core.PersistentVolumeClaim{
   625  		Status: core.PersistentVolumeClaimStatus{
   626  			AllocatedResourceStatuses: map[core.ResourceName]core.ClaimResourceStatus{
   627  				core.ResourceStorage: status,
   628  			},
   629  		},
   630  	}
   631  }
   632  
   633  func withVolumeAttributesClassName(vacName string) *core.PersistentVolumeClaim {
   634  	return &core.PersistentVolumeClaim{
   635  		Status: core.PersistentVolumeClaimStatus{
   636  			CurrentVolumeAttributesClassName: &vacName,
   637  		},
   638  	}
   639  }
   640  
   641  func withVolumeAttributesModifyStatus(target string, status core.PersistentVolumeClaimModifyVolumeStatus) *core.PersistentVolumeClaim {
   642  	return &core.PersistentVolumeClaim{
   643  		Status: core.PersistentVolumeClaimStatus{
   644  			ModifyVolumeStatus: &core.ModifyVolumeStatus{
   645  				TargetVolumeAttributesClassName: target,
   646  				Status:                          status,
   647  			},
   648  		},
   649  	}
   650  }
   651  
   652  func TestWarnings(t *testing.T) {
   653  	testcases := []struct {
   654  		name     string
   655  		template *core.PersistentVolumeClaim
   656  		expected []string
   657  	}{
   658  		{
   659  			name:     "null",
   660  			template: nil,
   661  			expected: nil,
   662  		},
   663  		{
   664  			name: "200Mi requests no warning",
   665  			template: &core.PersistentVolumeClaim{
   666  				Spec: core.PersistentVolumeClaimSpec{
   667  					Resources: core.VolumeResourceRequirements{
   668  						Requests: core.ResourceList{
   669  							core.ResourceStorage: resource.MustParse("200Mi"),
   670  						},
   671  						Limits: core.ResourceList{
   672  							core.ResourceStorage: resource.MustParse("200Mi"),
   673  						},
   674  					},
   675  				},
   676  			},
   677  			expected: nil,
   678  		},
   679  		{
   680  			name: "200m warning",
   681  			template: &core.PersistentVolumeClaim{
   682  				Spec: core.PersistentVolumeClaimSpec{
   683  					Resources: core.VolumeResourceRequirements{
   684  						Requests: core.ResourceList{
   685  							core.ResourceStorage: resource.MustParse("200m"),
   686  						},
   687  						Limits: core.ResourceList{
   688  							core.ResourceStorage: resource.MustParse("100m"),
   689  						},
   690  					},
   691  				},
   692  			},
   693  			expected: []string{
   694  				`spec.resources.requests[storage]: fractional byte value "200m" is invalid, must be an integer`,
   695  				`spec.resources.limits[storage]: fractional byte value "100m" is invalid, must be an integer`,
   696  			},
   697  		},
   698  		{
   699  			name: "integer no warning",
   700  			template: &core.PersistentVolumeClaim{
   701  				Spec: core.PersistentVolumeClaimSpec{
   702  					Resources: core.VolumeResourceRequirements{
   703  						Requests: core.ResourceList{
   704  							core.ResourceStorage: resource.MustParse("200"),
   705  						},
   706  					},
   707  				},
   708  			},
   709  			expected: nil,
   710  		},
   711  		{
   712  			name: "storageclass annotations warning",
   713  			template: &core.PersistentVolumeClaim{
   714  				ObjectMeta: metav1.ObjectMeta{
   715  					Name: "foo",
   716  					Annotations: map[string]string{
   717  						core.BetaStorageClassAnnotation: "",
   718  					},
   719  				},
   720  			},
   721  			expected: []string{
   722  				`metadata.annotations[volume.beta.kubernetes.io/storage-class]: deprecated since v1.8; use "storageClassName" attribute instead`,
   723  			},
   724  		},
   725  	}
   726  
   727  	for _, tc := range testcases {
   728  		t.Run("pvcspec_"+tc.name, func(t *testing.T) {
   729  			actual := sets.NewString(GetWarningsForPersistentVolumeClaim(tc.template)...)
   730  			expected := sets.NewString(tc.expected...)
   731  			for _, missing := range expected.Difference(actual).List() {
   732  				t.Errorf("missing: %s", missing)
   733  			}
   734  			for _, extra := range actual.Difference(expected).List() {
   735  				t.Errorf("extra: %s", extra)
   736  			}
   737  		})
   738  
   739  	}
   740  }