k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/storage/persistentvolume/resize/admission_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 resize 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "testing" 24 25 storagev1 "k8s.io/api/storage/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apiserver/pkg/admission" 31 "k8s.io/client-go/informers" 32 api "k8s.io/kubernetes/pkg/apis/core" 33 "k8s.io/kubernetes/pkg/controller" 34 ) 35 36 func getResourceList(storage string) api.ResourceList { 37 res := api.ResourceList{} 38 if storage != "" { 39 res[api.ResourceStorage] = resource.MustParse(storage) 40 } 41 return res 42 } 43 44 func TestPVCResizeAdmission(t *testing.T) { 45 goldClassName := "gold" 46 trueVal := true 47 falseVar := false 48 goldClass := &storagev1.StorageClass{ 49 TypeMeta: metav1.TypeMeta{ 50 Kind: "StorageClass", 51 }, 52 ObjectMeta: metav1.ObjectMeta{ 53 Name: goldClassName, 54 }, 55 Provisioner: "kubernetes.io/glusterfs", 56 AllowVolumeExpansion: &trueVal, 57 } 58 silverClassName := "silver" 59 silverClass := &storagev1.StorageClass{ 60 TypeMeta: metav1.TypeMeta{ 61 Kind: "StorageClass", 62 }, 63 ObjectMeta: metav1.ObjectMeta{ 64 Name: silverClassName, 65 }, 66 Provisioner: "kubernetes.io/glusterfs", 67 AllowVolumeExpansion: &falseVar, 68 } 69 expectNoError := func(err error) bool { 70 return err == nil 71 } 72 expectDynamicallyProvisionedError := func(err error) bool { 73 return strings.Contains(err.Error(), "only dynamically provisioned pvc can be resized and "+ 74 "the storageclass that provisions the pvc must support resize") 75 } 76 tests := []struct { 77 name string 78 resource schema.GroupVersionResource 79 subresource string 80 oldObj runtime.Object 81 newObj runtime.Object 82 83 checkError func(error) bool 84 }{ 85 { 86 name: "pvc-resize, update, no error", 87 resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), 88 oldObj: &api.PersistentVolumeClaim{ 89 Spec: api.PersistentVolumeClaimSpec{ 90 VolumeName: "volume1", 91 Resources: api.VolumeResourceRequirements{ 92 Requests: getResourceList("1Gi"), 93 }, 94 StorageClassName: &goldClassName, 95 }, 96 Status: api.PersistentVolumeClaimStatus{ 97 Capacity: getResourceList("1Gi"), 98 Phase: api.ClaimBound, 99 }, 100 }, 101 newObj: &api.PersistentVolumeClaim{ 102 Spec: api.PersistentVolumeClaimSpec{ 103 VolumeName: "volume1", 104 Resources: api.VolumeResourceRequirements{ 105 Requests: getResourceList("2Gi"), 106 }, 107 StorageClassName: &goldClassName, 108 }, 109 Status: api.PersistentVolumeClaimStatus{ 110 Capacity: getResourceList("2Gi"), 111 Phase: api.ClaimBound, 112 }, 113 }, 114 checkError: expectNoError, 115 }, 116 { 117 name: "pvc-resize, update, dynamically provisioned error", 118 resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), 119 oldObj: &api.PersistentVolumeClaim{ 120 Spec: api.PersistentVolumeClaimSpec{ 121 VolumeName: "volume3", 122 Resources: api.VolumeResourceRequirements{ 123 Requests: getResourceList("1Gi"), 124 }, 125 }, 126 Status: api.PersistentVolumeClaimStatus{ 127 Capacity: getResourceList("1Gi"), 128 Phase: api.ClaimBound, 129 }, 130 }, 131 newObj: &api.PersistentVolumeClaim{ 132 Spec: api.PersistentVolumeClaimSpec{ 133 VolumeName: "volume3", 134 Resources: api.VolumeResourceRequirements{ 135 Requests: getResourceList("2Gi"), 136 }, 137 }, 138 Status: api.PersistentVolumeClaimStatus{ 139 Capacity: getResourceList("2Gi"), 140 Phase: api.ClaimBound, 141 }, 142 }, 143 checkError: expectDynamicallyProvisionedError, 144 }, 145 { 146 name: "pvc-resize, update, dynamically provisioned error", 147 resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), 148 oldObj: &api.PersistentVolumeClaim{ 149 Spec: api.PersistentVolumeClaimSpec{ 150 VolumeName: "volume4", 151 Resources: api.VolumeResourceRequirements{ 152 Requests: getResourceList("1Gi"), 153 }, 154 StorageClassName: &silverClassName, 155 }, 156 Status: api.PersistentVolumeClaimStatus{ 157 Capacity: getResourceList("1Gi"), 158 Phase: api.ClaimBound, 159 }, 160 }, 161 newObj: &api.PersistentVolumeClaim{ 162 Spec: api.PersistentVolumeClaimSpec{ 163 VolumeName: "volume4", 164 Resources: api.VolumeResourceRequirements{ 165 Requests: getResourceList("2Gi"), 166 }, 167 StorageClassName: &silverClassName, 168 }, 169 Status: api.PersistentVolumeClaimStatus{ 170 Capacity: getResourceList("2Gi"), 171 Phase: api.ClaimBound, 172 }, 173 }, 174 checkError: expectDynamicallyProvisionedError, 175 }, 176 { 177 name: "PVC update with no change in size", 178 resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), 179 oldObj: &api.PersistentVolumeClaim{ 180 Spec: api.PersistentVolumeClaimSpec{ 181 Resources: api.VolumeResourceRequirements{ 182 Requests: getResourceList("1Gi"), 183 }, 184 StorageClassName: &silverClassName, 185 }, 186 Status: api.PersistentVolumeClaimStatus{ 187 Capacity: getResourceList("0Gi"), 188 Phase: api.ClaimPending, 189 }, 190 }, 191 newObj: &api.PersistentVolumeClaim{ 192 Spec: api.PersistentVolumeClaimSpec{ 193 VolumeName: "volume4", 194 Resources: api.VolumeResourceRequirements{ 195 Requests: getResourceList("1Gi"), 196 }, 197 StorageClassName: &silverClassName, 198 }, 199 Status: api.PersistentVolumeClaimStatus{ 200 Capacity: getResourceList("1Gi"), 201 Phase: api.ClaimBound, 202 }, 203 }, 204 checkError: expectNoError, 205 }, 206 { 207 name: "expand pvc in pending state", 208 resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), 209 oldObj: &api.PersistentVolumeClaim{ 210 Spec: api.PersistentVolumeClaimSpec{ 211 Resources: api.VolumeResourceRequirements{ 212 Requests: getResourceList("1Gi"), 213 }, 214 StorageClassName: &silverClassName, 215 }, 216 Status: api.PersistentVolumeClaimStatus{ 217 Capacity: getResourceList("0Gi"), 218 Phase: api.ClaimPending, 219 }, 220 }, 221 newObj: &api.PersistentVolumeClaim{ 222 Spec: api.PersistentVolumeClaimSpec{ 223 Resources: api.VolumeResourceRequirements{ 224 Requests: getResourceList("2Gi"), 225 }, 226 StorageClassName: &silverClassName, 227 }, 228 Status: api.PersistentVolumeClaimStatus{ 229 Capacity: getResourceList("0Gi"), 230 Phase: api.ClaimPending, 231 }, 232 }, 233 checkError: func(err error) bool { 234 return strings.Contains(err.Error(), "Only bound persistent volume claims can be expanded") 235 }, 236 }, 237 } 238 239 ctrl := newPlugin() 240 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 241 ctrl.SetExternalKubeInformerFactory(informerFactory) 242 err := ctrl.ValidateInitialization() 243 if err != nil { 244 t.Fatalf("neither pv lister nor storageclass lister can be nil") 245 } 246 247 scs := []*storagev1.StorageClass{} 248 scs = append(scs, goldClass, silverClass) 249 for _, sc := range scs { 250 err := informerFactory.Storage().V1().StorageClasses().Informer().GetStore().Add(sc) 251 if err != nil { 252 fmt.Println("add storageclass error: ", err) 253 } 254 } 255 256 for _, tc := range tests { 257 operation := admission.Update 258 operationOptions := &metav1.CreateOptions{} 259 attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, operationOptions, false, nil) 260 261 err := ctrl.Validate(context.TODO(), attributes, nil) 262 if !tc.checkError(err) { 263 t.Errorf("%v: unexpected err: %v", tc.name, err) 264 } 265 } 266 267 }