k8s.io/kubernetes@v1.29.3/pkg/controller/volume/expand/expand_controller_test.go (about) 1 /* 2 Copyright 2019 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 expand 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "regexp" 25 "testing" 26 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/resource" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/strategicpatch" 33 utilfeature "k8s.io/apiserver/pkg/util/feature" 34 "k8s.io/client-go/informers" 35 coretesting "k8s.io/client-go/testing" 36 csitrans "k8s.io/csi-translation-lib" 37 csitranslationplugins "k8s.io/csi-translation-lib/plugins" 38 "k8s.io/kubernetes/pkg/controller" 39 controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing" 40 "k8s.io/kubernetes/pkg/volume" 41 "k8s.io/kubernetes/pkg/volume/csimigration" 42 "k8s.io/kubernetes/pkg/volume/util" 43 "k8s.io/kubernetes/pkg/volume/util/operationexecutor" 44 volumetypes "k8s.io/kubernetes/pkg/volume/util/types" 45 ) 46 47 func TestSyncHandler(t *testing.T) { 48 tests := []struct { 49 name string 50 pvcKey string 51 pv *v1.PersistentVolume 52 pvc *v1.PersistentVolumeClaim 53 expansionCalled bool 54 hasError bool 55 expectedAnnotation map[string]string 56 }{ 57 { 58 name: "when pvc has no PV binding", 59 pvc: getFakePersistentVolumeClaim("no-pv-pvc", "", "1Gi", "1Gi", ""), 60 pvcKey: "default/no-pv-pvc", 61 hasError: true, 62 }, 63 { 64 name: "when pvc and pv has everything for in-tree plugin", 65 pv: getFakePersistentVolume("vol-3", csitranslationplugins.AWSEBSInTreePluginName, "1Gi", "good-pvc-vol-3"), 66 pvc: getFakePersistentVolumeClaim("good-pvc", "vol-3", "1Gi", "2Gi", "good-pvc-vol-3"), 67 pvcKey: "default/good-pvc", 68 expansionCalled: false, 69 expectedAnnotation: map[string]string{volumetypes.VolumeResizerKey: csitranslationplugins.AWSEBSDriverName}, 70 }, 71 { 72 name: "if pv has pre-resize capacity annotation, generate expand operation should not be called", 73 pv: func() *v1.PersistentVolume { 74 pv := getFakePersistentVolume("vol-4", csitranslationplugins.AWSEBSInTreePluginName, "2Gi", "good-pvc-vol-4") 75 pv.ObjectMeta.Annotations = make(map[string]string) 76 pv.ObjectMeta.Annotations[util.AnnPreResizeCapacity] = "1Gi" 77 return pv 78 }(), 79 pvc: getFakePersistentVolumeClaim("good-pvc", "vol-4", "2Gi", "2Gi", "good-pvc-vol-4"), 80 pvcKey: "default/good-pvc", 81 expansionCalled: false, 82 }, 83 { 84 name: "for csi plugin without migration path", 85 pv: getFakePersistentVolume("vol-5", "com.csi.ceph", "1Gi", "ceph-csi-pvc-vol-6"), 86 pvc: getFakePersistentVolumeClaim("ceph-csi-pvc", "vol-5", "1Gi", "2Gi", "ceph-csi-pvc-vol-6"), 87 pvcKey: "default/ceph-csi-pvc", 88 expansionCalled: false, 89 hasError: false, 90 }, 91 } 92 93 for _, tc := range tests { 94 test := tc 95 fakeKubeClient := controllervolumetesting.CreateTestClient() 96 informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) 97 pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() 98 99 pvc := test.pvc 100 if tc.pv != nil { 101 informerFactory.Core().V1().PersistentVolumes().Informer().GetIndexer().Add(tc.pv) 102 } 103 104 if tc.pvc != nil { 105 informerFactory.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc) 106 } 107 allPlugins := []volume.VolumePlugin{} 108 translator := csitrans.New() 109 expc, err := NewExpandController(fakeKubeClient, pvcInformer, nil, allPlugins, translator, csimigration.NewPluginManager(translator, utilfeature.DefaultFeatureGate)) 110 if err != nil { 111 t.Fatalf("error creating expand controller : %v", err) 112 } 113 114 var expController *expandController 115 expController, _ = expc.(*expandController) 116 var expansionCalled bool 117 expController.operationGenerator = operationexecutor.NewFakeOGCounter(func() volumetypes.OperationContext { 118 expansionCalled = true 119 return volumetypes.NewOperationContext(nil, nil, false) 120 }) 121 122 if test.pv != nil { 123 fakeKubeClient.AddReactor("get", "persistentvolumes", func(action coretesting.Action) (bool, runtime.Object, error) { 124 return true, test.pv, nil 125 }) 126 } 127 fakeKubeClient.AddReactor("patch", "persistentvolumeclaims", func(action coretesting.Action) (bool, runtime.Object, error) { 128 if action.GetSubresource() == "status" { 129 patchActionaction, _ := action.(coretesting.PatchAction) 130 pvc, err = applyPVCPatch(pvc, patchActionaction.GetPatch()) 131 if err != nil { 132 return false, nil, err 133 } 134 return true, pvc, nil 135 } 136 return true, pvc, nil 137 }) 138 139 err = expController.syncHandler(context.TODO(), test.pvcKey) 140 if err != nil && !test.hasError { 141 t.Fatalf("for: %s; unexpected error while running handler : %v", test.name, err) 142 } 143 144 if err == nil && test.hasError { 145 t.Fatalf("for: %s; unexpected success", test.name) 146 } 147 if expansionCalled != test.expansionCalled { 148 t.Fatalf("for: %s; expected expansionCalled to be %v but was %v", test.name, test.expansionCalled, expansionCalled) 149 } 150 151 if len(test.expectedAnnotation) != 0 && !reflect.DeepEqual(test.expectedAnnotation, pvc.Annotations) { 152 t.Fatalf("for: %s; expected %v annotations, got %v", test.name, test.expectedAnnotation, pvc.Annotations) 153 } 154 } 155 } 156 157 func applyPVCPatch(originalPVC *v1.PersistentVolumeClaim, patch []byte) (*v1.PersistentVolumeClaim, error) { 158 pvcData, err := json.Marshal(originalPVC) 159 if err != nil { 160 return nil, fmt.Errorf("failed to marshal pvc with %v", err) 161 } 162 updated, err := strategicpatch.StrategicMergePatch(pvcData, patch, v1.PersistentVolumeClaim{}) 163 if err != nil { 164 return nil, fmt.Errorf("failed to apply patch on pvc %v", err) 165 } 166 updatedPVC := &v1.PersistentVolumeClaim{} 167 if err := json.Unmarshal(updated, updatedPVC); err != nil { 168 return nil, fmt.Errorf("failed to unmarshal updated pvc : %v", err) 169 } 170 return updatedPVC, nil 171 } 172 173 func getFakePersistentVolume(volumeName, pluginName string, size string, pvcUID types.UID) *v1.PersistentVolume { 174 pv := &v1.PersistentVolume{ 175 ObjectMeta: metav1.ObjectMeta{Name: volumeName}, 176 Spec: v1.PersistentVolumeSpec{ 177 PersistentVolumeSource: v1.PersistentVolumeSource{}, 178 ClaimRef: &v1.ObjectReference{ 179 Namespace: "default", 180 }, 181 Capacity: map[v1.ResourceName]resource.Quantity{ 182 v1.ResourceStorage: resource.MustParse(size), 183 }, 184 }, 185 } 186 if pvcUID != "" { 187 pv.Spec.ClaimRef.UID = pvcUID 188 } 189 190 if matched, _ := regexp.MatchString(`csi`, pluginName); matched { 191 pv.Spec.PersistentVolumeSource.CSI = &v1.CSIPersistentVolumeSource{ 192 Driver: pluginName, 193 VolumeHandle: volumeName, 194 } 195 } else { 196 pv.Spec.PersistentVolumeSource.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{ 197 VolumeID: volumeName, 198 FSType: "ext4", 199 } 200 } 201 return pv 202 } 203 204 func getFakePersistentVolumeClaim(pvcName, volumeName, statusSize, requestSize string, uid types.UID) *v1.PersistentVolumeClaim { 205 pvc := &v1.PersistentVolumeClaim{ 206 ObjectMeta: metav1.ObjectMeta{Name: pvcName, Namespace: "default", UID: uid}, 207 Spec: v1.PersistentVolumeClaimSpec{ 208 Resources: v1.VolumeResourceRequirements{ 209 Requests: map[v1.ResourceName]resource.Quantity{ 210 v1.ResourceStorage: resource.MustParse(requestSize), 211 }, 212 }, 213 }, 214 Status: v1.PersistentVolumeClaimStatus{ 215 Capacity: map[v1.ResourceName]resource.Quantity{ 216 v1.ResourceStorage: resource.MustParse(statusSize), 217 }, 218 }, 219 } 220 if volumeName != "" { 221 pvc.Spec.VolumeName = volumeName 222 } 223 224 return pvc 225 }