k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/storage/storageclass/setdefault/admission_test.go (about) 1 /* 2 Copyright 2016 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 setdefault 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 "k8s.io/klog/v2" 25 26 storagev1 "k8s.io/api/storage/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apiserver/pkg/admission" 29 admissiontesting "k8s.io/apiserver/pkg/admission/testing" 30 "k8s.io/client-go/informers" 31 api "k8s.io/kubernetes/pkg/apis/core" 32 storageutil "k8s.io/kubernetes/pkg/apis/storage/util" 33 "k8s.io/kubernetes/pkg/controller" 34 ) 35 36 func TestAdmission(t *testing.T) { 37 empty := "" 38 foo := "foo" 39 40 defaultClass1 := &storagev1.StorageClass{ 41 TypeMeta: metav1.TypeMeta{ 42 Kind: "StorageClass", 43 }, 44 ObjectMeta: metav1.ObjectMeta{ 45 Name: "default1", 46 Annotations: map[string]string{ 47 storageutil.IsDefaultStorageClassAnnotation: "true", 48 }, 49 }, 50 Provisioner: "default1", 51 } 52 defaultClass2 := &storagev1.StorageClass{ 53 TypeMeta: metav1.TypeMeta{ 54 Kind: "StorageClass", 55 }, 56 ObjectMeta: metav1.ObjectMeta{ 57 Name: "default2", 58 Annotations: map[string]string{ 59 storageutil.IsDefaultStorageClassAnnotation: "true", 60 }, 61 }, 62 Provisioner: "default2", 63 } 64 // Class that has explicit default = false 65 classWithFalseDefault := &storagev1.StorageClass{ 66 TypeMeta: metav1.TypeMeta{ 67 Kind: "StorageClass", 68 }, 69 ObjectMeta: metav1.ObjectMeta{ 70 Name: "nondefault1", 71 Annotations: map[string]string{ 72 storageutil.IsDefaultStorageClassAnnotation: "false", 73 }, 74 }, 75 Provisioner: "nondefault1", 76 } 77 // Class with missing default annotation (=non-default) 78 classWithNoDefault := &storagev1.StorageClass{ 79 TypeMeta: metav1.TypeMeta{ 80 Kind: "StorageClass", 81 }, 82 ObjectMeta: metav1.ObjectMeta{ 83 Name: "nondefault2", 84 }, 85 Provisioner: "nondefault1", 86 } 87 // Class with empty default annotation (=non-default) 88 classWithEmptyDefault := &storagev1.StorageClass{ 89 TypeMeta: metav1.TypeMeta{ 90 Kind: "StorageClass", 91 }, 92 ObjectMeta: metav1.ObjectMeta{ 93 Name: "nondefault2", 94 Annotations: map[string]string{ 95 storageutil.IsDefaultStorageClassAnnotation: "", 96 }, 97 }, 98 Provisioner: "nondefault1", 99 } 100 classWithCreateTime1 := &storagev1.StorageClass{ 101 TypeMeta: metav1.TypeMeta{ 102 Kind: "StorageClass", 103 }, 104 ObjectMeta: metav1.ObjectMeta{ 105 Name: "default1", 106 CreationTimestamp: metav1.NewTime(time.Date(2022, time.Month(1), 1, 0, 0, 0, 1, time.UTC)), 107 Annotations: map[string]string{ 108 storageutil.IsDefaultStorageClassAnnotation: "true", 109 }, 110 }, 111 } 112 classWithCreateTime2 := &storagev1.StorageClass{ 113 TypeMeta: metav1.TypeMeta{ 114 Kind: "StorageClass", 115 }, 116 ObjectMeta: metav1.ObjectMeta{ 117 Name: "default2", 118 CreationTimestamp: metav1.NewTime(time.Date(2022, time.Month(1), 1, 0, 0, 0, 0, time.UTC)), 119 Annotations: map[string]string{ 120 storageutil.IsDefaultStorageClassAnnotation: "true", 121 }, 122 }, 123 } 124 125 claimWithClass := &api.PersistentVolumeClaim{ 126 TypeMeta: metav1.TypeMeta{ 127 Kind: "PersistentVolumeClaim", 128 }, 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: "claimWithClass", 131 Namespace: "ns", 132 }, 133 Spec: api.PersistentVolumeClaimSpec{ 134 StorageClassName: &foo, 135 }, 136 } 137 claimWithEmptyClass := &api.PersistentVolumeClaim{ 138 TypeMeta: metav1.TypeMeta{ 139 Kind: "PersistentVolumeClaim", 140 }, 141 ObjectMeta: metav1.ObjectMeta{ 142 Name: "claimWithEmptyClass", 143 Namespace: "ns", 144 }, 145 Spec: api.PersistentVolumeClaimSpec{ 146 StorageClassName: &empty, 147 }, 148 } 149 claimWithNoClass := &api.PersistentVolumeClaim{ 150 TypeMeta: metav1.TypeMeta{ 151 Kind: "PersistentVolumeClaim", 152 }, 153 ObjectMeta: metav1.ObjectMeta{ 154 Name: "claimWithNoClass", 155 Namespace: "ns", 156 }, 157 } 158 159 tests := []struct { 160 name string 161 classes []*storagev1.StorageClass 162 claim *api.PersistentVolumeClaim 163 expectError bool 164 expectedClassName string 165 }{ 166 { 167 "no default, no modification of PVCs", 168 []*storagev1.StorageClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 169 claimWithNoClass, 170 false, 171 "", 172 }, 173 { 174 "one default, modify PVC with class=nil", 175 []*storagev1.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 176 claimWithNoClass, 177 false, 178 "default1", 179 }, 180 { 181 "one default, no modification of PVC with class=''", 182 []*storagev1.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 183 claimWithEmptyClass, 184 false, 185 "", 186 }, 187 { 188 "one default, no modification of PVC with class='foo'", 189 []*storagev1.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 190 claimWithClass, 191 false, 192 "foo", 193 }, 194 { 195 "two defaults, no modification of PVC with class=''", 196 []*storagev1.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 197 claimWithEmptyClass, 198 false, 199 "", 200 }, 201 { 202 "two defaults, no modification of PVC with class='foo'", 203 []*storagev1.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 204 claimWithClass, 205 false, 206 "foo", 207 }, 208 { 209 "two defaults with same creation time, choose the one with smaller name", 210 []*storagev1.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 211 claimWithNoClass, 212 false, 213 defaultClass1.Name, 214 }, 215 { 216 "two defaults, choose the one with newer creation time", 217 []*storagev1.StorageClass{classWithCreateTime1, classWithCreateTime2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 218 claimWithNoClass, 219 false, 220 classWithCreateTime1.Name, 221 }, 222 } 223 224 for _, test := range tests { 225 klog.V(4).Infof("starting test %q", test.name) 226 227 // clone the claim, it's going to be modified 228 claim := test.claim.DeepCopy() 229 230 ctrl := newPlugin() 231 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 232 ctrl.SetExternalKubeInformerFactory(informerFactory) 233 for _, c := range test.classes { 234 informerFactory.Storage().V1().StorageClasses().Informer().GetStore().Add(c) 235 } 236 attrs := admission.NewAttributesRecord( 237 claim, // new object 238 nil, // old object 239 api.Kind("PersistentVolumeClaim").WithVersion("version"), 240 claim.Namespace, 241 claim.Name, 242 api.Resource("persistentvolumeclaims").WithVersion("version"), 243 "", // subresource 244 admission.Create, 245 &metav1.CreateOptions{}, 246 false, // dryRun 247 nil, // userInfo 248 ) 249 err := admissiontesting.WithReinvocationTesting(t, ctrl).Admit(context.TODO(), attrs, nil) 250 klog.Infof("Got %v", err) 251 if err != nil && !test.expectError { 252 t.Errorf("Test %q: unexpected error received: %v", test.name, err) 253 } 254 if err == nil && test.expectError { 255 t.Errorf("Test %q: expected error and no error recevied", test.name) 256 } 257 258 class := "" 259 if claim.Spec.StorageClassName != nil { 260 class = *claim.Spec.StorageClassName 261 } 262 if test.expectedClassName != "" && test.expectedClassName != class { 263 t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class) 264 } 265 if test.expectedClassName == "" && class != "" { 266 t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class) 267 } 268 } 269 }