k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/api/persistentvolume/util_test.go (about)

     1  /*
     2  Copyright 2018 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 persistentvolume
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    28  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    29  	api "k8s.io/kubernetes/pkg/apis/core"
    30  	"k8s.io/kubernetes/pkg/features"
    31  	"k8s.io/utils/ptr"
    32  )
    33  
    34  func TestDropDisabledFields(t *testing.T) {
    35  	vacName := ptr.To("vac")
    36  
    37  	tests := map[string]struct {
    38  		oldSpec       *api.PersistentVolumeSpec
    39  		newSpec       *api.PersistentVolumeSpec
    40  		expectOldSpec *api.PersistentVolumeSpec
    41  		expectNewSpec *api.PersistentVolumeSpec
    42  		vacEnabled    bool
    43  	}{
    44  		"disabled vac clears volume attributes class name": {
    45  			vacEnabled:    false,
    46  			newSpec:       specWithVACName(vacName),
    47  			expectNewSpec: specWithVACName(nil),
    48  			oldSpec:       nil,
    49  			expectOldSpec: nil,
    50  		},
    51  		"enabled vac preserve volume attributes class name": {
    52  			vacEnabled:    true,
    53  			newSpec:       specWithVACName(vacName),
    54  			expectNewSpec: specWithVACName(vacName),
    55  			oldSpec:       nil,
    56  			expectOldSpec: nil,
    57  		},
    58  		"enabled vac preserve volume attributes class name when both old and new have it": {
    59  			vacEnabled:    true,
    60  			newSpec:       specWithVACName(vacName),
    61  			expectNewSpec: specWithVACName(vacName),
    62  			oldSpec:       specWithVACName(vacName),
    63  			expectOldSpec: specWithVACName(vacName),
    64  		},
    65  		"disabled vac old pv had volume attributes class name": {
    66  			vacEnabled:    false,
    67  			newSpec:       specWithVACName(vacName),
    68  			expectNewSpec: specWithVACName(vacName),
    69  			oldSpec:       specWithVACName(vacName),
    70  			expectOldSpec: specWithVACName(vacName),
    71  		},
    72  		"enabled vac preserves volume attributes class name when old pv did not had it": {
    73  			vacEnabled:    true,
    74  			newSpec:       specWithVACName(vacName),
    75  			expectNewSpec: specWithVACName(vacName),
    76  			oldSpec:       specWithVACName(nil),
    77  			expectOldSpec: specWithVACName(nil),
    78  		},
    79  		"disabled vac neither new pv nor old pv had volume attributes class name": {
    80  			vacEnabled:    false,
    81  			newSpec:       specWithVACName(nil),
    82  			expectNewSpec: specWithVACName(nil),
    83  			oldSpec:       specWithVACName(nil),
    84  			expectOldSpec: specWithVACName(nil),
    85  		},
    86  	}
    87  
    88  	for name, tc := range tests {
    89  		t.Run(name, func(t *testing.T) {
    90  			featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, tc.vacEnabled)
    91  
    92  			DropDisabledSpecFields(tc.newSpec, tc.oldSpec)
    93  			if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) {
    94  				t.Error(cmp.Diff(tc.newSpec, tc.expectNewSpec))
    95  			}
    96  			if !reflect.DeepEqual(tc.oldSpec, tc.expectOldSpec) {
    97  				t.Error(cmp.Diff(tc.oldSpec, tc.expectOldSpec))
    98  			}
    99  		})
   100  	}
   101  }
   102  
   103  func specWithVACName(vacName *string) *api.PersistentVolumeSpec {
   104  	pvSpec := &api.PersistentVolumeSpec{
   105  		PersistentVolumeSource: api.PersistentVolumeSource{
   106  			CSI: &api.CSIPersistentVolumeSource{
   107  				Driver:       "com.google.gcepd",
   108  				VolumeHandle: "foobar",
   109  			},
   110  		},
   111  	}
   112  
   113  	if vacName != nil {
   114  		pvSpec.VolumeAttributesClassName = vacName
   115  	}
   116  	return pvSpec
   117  }
   118  
   119  func TestWarnings(t *testing.T) {
   120  	testcases := []struct {
   121  		name     string
   122  		template *api.PersistentVolume
   123  		expected []string
   124  	}{
   125  		{
   126  			name:     "null",
   127  			template: nil,
   128  			expected: nil,
   129  		},
   130  		{
   131  			name: "no warning",
   132  			template: &api.PersistentVolume{
   133  				ObjectMeta: metav1.ObjectMeta{
   134  					Name: "foo",
   135  				},
   136  				Status: api.PersistentVolumeStatus{
   137  					Phase: api.VolumeBound,
   138  				},
   139  			},
   140  			expected: nil,
   141  		},
   142  		{
   143  			name: "warning",
   144  			template: &api.PersistentVolume{
   145  				ObjectMeta: metav1.ObjectMeta{
   146  					Name: "foo",
   147  					Annotations: map[string]string{
   148  						api.BetaStorageClassAnnotation: "",
   149  					},
   150  				},
   151  				Spec: api.PersistentVolumeSpec{
   152  					NodeAffinity: &api.VolumeNodeAffinity{
   153  						Required: &api.NodeSelector{
   154  							NodeSelectorTerms: []api.NodeSelectorTerm{
   155  								{
   156  									MatchExpressions: []api.NodeSelectorRequirement{
   157  										{
   158  											Key:      "beta.kubernetes.io/os",
   159  											Operator: "Equal",
   160  											Values:   []string{"windows"},
   161  										},
   162  									},
   163  								},
   164  							},
   165  						},
   166  					},
   167  				},
   168  				Status: api.PersistentVolumeStatus{
   169  					Phase: api.VolumeBound,
   170  				},
   171  			},
   172  			expected: []string{
   173  				`metadata.annotations[volume.beta.kubernetes.io/storage-class]: deprecated since v1.8; use "storageClassName" attribute instead`,
   174  				`spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
   175  			},
   176  		},
   177  		{
   178  			name: "PersistentVolumeReclaimRecycle deprecation warning",
   179  			template: &api.PersistentVolume{
   180  				Spec: api.PersistentVolumeSpec{
   181  					PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle,
   182  				},
   183  			},
   184  			expected: []string{
   185  				`spec.persistentVolumeReclaimPolicy: The Recycle reclaim policy is deprecated. Instead, the recommended approach is to use dynamic provisioning.`,
   186  			},
   187  		},
   188  		{
   189  			name: "PV CephFS deprecation warning",
   190  			template: &api.PersistentVolume{
   191  				Spec: api.PersistentVolumeSpec{
   192  					PersistentVolumeSource: api.PersistentVolumeSource{
   193  						CephFS: &api.CephFSPersistentVolumeSource{
   194  							Monitors:   nil,
   195  							Path:       "",
   196  							User:       "",
   197  							SecretFile: "",
   198  							SecretRef:  nil,
   199  							ReadOnly:   false,
   200  						},
   201  					},
   202  				},
   203  			},
   204  			expected: []string{
   205  				`spec.cephfs: deprecated in v1.28, non-functional in v1.31+`,
   206  			},
   207  		},
   208  		{
   209  			name: "PV PhotonPersistentDisk deprecation warning",
   210  			template: &api.PersistentVolume{
   211  				Spec: api.PersistentVolumeSpec{
   212  					PersistentVolumeSource: api.PersistentVolumeSource{
   213  						PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{
   214  							PdID:   "",
   215  							FSType: "",
   216  						},
   217  					},
   218  				},
   219  			},
   220  			expected: []string{
   221  				`spec.photonPersistentDisk: deprecated in v1.11, non-functional in v1.16+`,
   222  			},
   223  		},
   224  		{
   225  			name: "PV RBD deprecation warning",
   226  			template: &api.PersistentVolume{
   227  				Spec: api.PersistentVolumeSpec{
   228  					PersistentVolumeSource: api.PersistentVolumeSource{
   229  						RBD: &api.RBDPersistentVolumeSource{
   230  							CephMonitors: nil,
   231  							RBDImage:     "",
   232  							FSType:       "",
   233  							RBDPool:      "",
   234  							RadosUser:    "",
   235  							Keyring:      "",
   236  							SecretRef:    nil,
   237  							ReadOnly:     false,
   238  						},
   239  					},
   240  				},
   241  			},
   242  			expected: []string{
   243  				`spec.rbd: deprecated in v1.28, non-functional in v1.31+`},
   244  		},
   245  		{
   246  			name: "PV ScaleIO deprecation warning",
   247  			template: &api.PersistentVolume{
   248  				Spec: api.PersistentVolumeSpec{
   249  					PersistentVolumeSource: api.PersistentVolumeSource{
   250  						ScaleIO: &api.ScaleIOPersistentVolumeSource{
   251  							Gateway:          "",
   252  							System:           "",
   253  							SecretRef:        nil,
   254  							SSLEnabled:       false,
   255  							ProtectionDomain: "",
   256  							StoragePool:      "",
   257  							StorageMode:      "",
   258  							VolumeName:       "",
   259  							FSType:           "",
   260  							ReadOnly:         false,
   261  						},
   262  					},
   263  				},
   264  			},
   265  			expected: []string{
   266  				`spec.scaleIO: deprecated in v1.16, non-functional in v1.22+`,
   267  			},
   268  		},
   269  		{
   270  			name: "PV StorageOS deprecation warning",
   271  			template: &api.PersistentVolume{
   272  				Spec: api.PersistentVolumeSpec{
   273  					PersistentVolumeSource: api.PersistentVolumeSource{
   274  						StorageOS: &api.StorageOSPersistentVolumeSource{
   275  							VolumeName:      "",
   276  							VolumeNamespace: "",
   277  							FSType:          "",
   278  							ReadOnly:        false,
   279  							SecretRef:       nil,
   280  						},
   281  					},
   282  				},
   283  			},
   284  			expected: []string{
   285  				`spec.storageOS: deprecated in v1.22, non-functional in v1.25+`,
   286  			},
   287  		},
   288  		{
   289  			name: "PV GlusterFS deprecation warning",
   290  			template: &api.PersistentVolume{
   291  				Spec: api.PersistentVolumeSpec{
   292  					PersistentVolumeSource: api.PersistentVolumeSource{
   293  						Glusterfs: &api.GlusterfsPersistentVolumeSource{
   294  							EndpointsName:      "",
   295  							Path:               "",
   296  							ReadOnly:           false,
   297  							EndpointsNamespace: nil,
   298  						},
   299  					},
   300  				},
   301  			},
   302  			expected: []string{
   303  				`spec.glusterfs: deprecated in v1.25, non-functional in v1.26+`,
   304  			},
   305  		},
   306  	}
   307  
   308  	for _, tc := range testcases {
   309  		t.Run("podspec_"+tc.name, func(t *testing.T) {
   310  			actual := sets.New[string](GetWarningsForPersistentVolume(tc.template)...)
   311  			expected := sets.New[string](tc.expected...)
   312  			for _, missing := range sets.List[string](expected.Difference(actual)) {
   313  				t.Errorf("missing: %s", missing)
   314  			}
   315  			for _, extra := range sets.List[string](actual.Difference(expected)) {
   316  				t.Errorf("extra: %s", extra)
   317  			}
   318  		})
   319  
   320  	}
   321  }