k8s.io/kubernetes@v1.29.3/pkg/volume/util/resize_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 util 18 19 import ( 20 "encoding/json" 21 "reflect" 22 "testing" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 clientset "k8s.io/client-go/kubernetes" 30 "k8s.io/client-go/kubernetes/fake" 31 featuregatetesting "k8s.io/component-base/featuregate/testing" 32 "k8s.io/kubernetes/pkg/features" 33 ) 34 35 type conditionMergeTestCase struct { 36 description string 37 pvc *v1.PersistentVolumeClaim 38 newConditions []v1.PersistentVolumeClaimCondition 39 finalConditions []v1.PersistentVolumeClaimCondition 40 } 41 42 func TestMergeResizeCondition(t *testing.T) { 43 currentTime := metav1.Now() 44 45 pvc := makePVC([]v1.PersistentVolumeClaimCondition{ 46 { 47 Type: v1.PersistentVolumeClaimResizing, 48 Status: v1.ConditionTrue, 49 LastTransitionTime: currentTime, 50 }, 51 }).get() 52 53 noConditionPVC := makePVC([]v1.PersistentVolumeClaimCondition{}).get() 54 55 conditionFalseTime := metav1.Now() 56 newTime := metav1.NewTime(time.Now().Add(1 * time.Hour)) 57 58 testCases := []conditionMergeTestCase{ 59 { 60 description: "when removing all conditions", 61 pvc: pvc.DeepCopy(), 62 newConditions: []v1.PersistentVolumeClaimCondition{}, 63 finalConditions: []v1.PersistentVolumeClaimCondition{}, 64 }, 65 { 66 description: "adding new condition", 67 pvc: pvc.DeepCopy(), 68 newConditions: []v1.PersistentVolumeClaimCondition{ 69 { 70 Type: v1.PersistentVolumeClaimFileSystemResizePending, 71 Status: v1.ConditionTrue, 72 }, 73 }, 74 finalConditions: []v1.PersistentVolumeClaimCondition{ 75 { 76 Type: v1.PersistentVolumeClaimFileSystemResizePending, 77 Status: v1.ConditionTrue, 78 }, 79 }, 80 }, 81 { 82 description: "adding same condition with new timestamp", 83 pvc: pvc.DeepCopy(), 84 newConditions: []v1.PersistentVolumeClaimCondition{ 85 { 86 Type: v1.PersistentVolumeClaimResizing, 87 Status: v1.ConditionTrue, 88 LastTransitionTime: newTime, 89 }, 90 }, 91 finalConditions: []v1.PersistentVolumeClaimCondition{ 92 { 93 Type: v1.PersistentVolumeClaimResizing, 94 Status: v1.ConditionTrue, 95 LastTransitionTime: currentTime, 96 }, 97 }, 98 }, 99 { 100 description: "adding same condition but with different status", 101 pvc: pvc.DeepCopy(), 102 newConditions: []v1.PersistentVolumeClaimCondition{ 103 { 104 Type: v1.PersistentVolumeClaimResizing, 105 Status: v1.ConditionFalse, 106 LastTransitionTime: conditionFalseTime, 107 }, 108 }, 109 finalConditions: []v1.PersistentVolumeClaimCondition{ 110 { 111 Type: v1.PersistentVolumeClaimResizing, 112 Status: v1.ConditionFalse, 113 LastTransitionTime: conditionFalseTime, 114 }, 115 }, 116 }, 117 { 118 description: "when no condition exists on pvc", 119 pvc: noConditionPVC.DeepCopy(), 120 newConditions: []v1.PersistentVolumeClaimCondition{ 121 { 122 Type: v1.PersistentVolumeClaimResizing, 123 Status: v1.ConditionTrue, 124 LastTransitionTime: currentTime, 125 }, 126 }, 127 finalConditions: []v1.PersistentVolumeClaimCondition{ 128 { 129 Type: v1.PersistentVolumeClaimResizing, 130 Status: v1.ConditionTrue, 131 LastTransitionTime: currentTime, 132 }, 133 }, 134 }, 135 } 136 137 for _, testcase := range testCases { 138 updatePVC := MergeResizeConditionOnPVC(testcase.pvc, testcase.newConditions) 139 140 updateConditions := updatePVC.Status.Conditions 141 if !reflect.DeepEqual(updateConditions, testcase.finalConditions) { 142 t.Errorf("Expected updated conditions for test %s to be %v but got %v", 143 testcase.description, 144 testcase.finalConditions, updateConditions) 145 } 146 } 147 148 } 149 150 func TestResizeFunctions(t *testing.T) { 151 basePVC := makePVC([]v1.PersistentVolumeClaimCondition{}) 152 153 tests := []struct { 154 name string 155 pvc *v1.PersistentVolumeClaim 156 expectedPVC *v1.PersistentVolumeClaim 157 testFunc func(*v1.PersistentVolumeClaim, clientset.Interface, resource.Quantity) (*v1.PersistentVolumeClaim, error) 158 }{ 159 { 160 name: "mark fs resize, with no other conditions", 161 pvc: basePVC.get(), 162 expectedPVC: basePVC.withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(), 163 testFunc: func(pvc *v1.PersistentVolumeClaim, c clientset.Interface, _ resource.Quantity) (*v1.PersistentVolumeClaim, error) { 164 return MarkForFSResize(pvc, c) 165 }, 166 }, 167 { 168 name: "mark fs resize, when other resource statuses are present", 169 pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed).get(), 170 expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed). 171 withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(), 172 testFunc: func(pvc *v1.PersistentVolumeClaim, c clientset.Interface, _ resource.Quantity) (*v1.PersistentVolumeClaim, error) { 173 return MarkForFSResize(pvc, c) 174 }, 175 }, 176 { 177 name: "mark controller resize in-progress", 178 pvc: basePVC.get(), 179 expectedPVC: basePVC.withStorageResourceStatus(v1.PersistentVolumeClaimControllerResizeInProgress).get(), 180 testFunc: func(pvc *v1.PersistentVolumeClaim, i clientset.Interface, q resource.Quantity) (*v1.PersistentVolumeClaim, error) { 181 return MarkControllerReisizeInProgress(pvc, "foobar", q, i) 182 }, 183 }, 184 { 185 name: "mark resize finished", 186 pvc: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed). 187 withStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).get(), 188 expectedPVC: basePVC.withResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeFailed). 189 withStorageResourceStatus("").get(), 190 testFunc: func(pvc *v1.PersistentVolumeClaim, i clientset.Interface, q resource.Quantity) (*v1.PersistentVolumeClaim, error) { 191 return MarkFSResizeFinished(pvc, q, i) 192 }, 193 }, 194 } 195 196 for _, test := range tests { 197 tc := test 198 t.Run(tc.name, func(t *testing.T) { 199 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RecoverVolumeExpansionFailure, true)() 200 pvc := tc.pvc 201 kubeClient := fake.NewSimpleClientset(pvc) 202 203 var err error 204 205 pvc, err = tc.testFunc(pvc, kubeClient, resource.MustParse("10Gi")) 206 if err != nil { 207 t.Errorf("Expected no error but got %v", err) 208 } 209 realStatus := pvc.Status.AllocatedResourceStatuses 210 expectedStatus := tc.expectedPVC.Status.AllocatedResourceStatuses 211 if !reflect.DeepEqual(realStatus, expectedStatus) { 212 t.Errorf("expected %+v got %+v", expectedStatus, realStatus) 213 } 214 }) 215 } 216 217 } 218 219 func TestCreatePVCPatch(t *testing.T) { 220 pvc1 := makePVC([]v1.PersistentVolumeClaimCondition{ 221 { 222 Type: v1.PersistentVolumeClaimFileSystemResizePending, 223 Status: v1.ConditionTrue, 224 LastTransitionTime: metav1.Now(), 225 }, 226 }).get() 227 pvc1.SetResourceVersion("10") 228 pvc2 := pvc1.DeepCopy() 229 pvc2.Status.Capacity = v1.ResourceList{ 230 v1.ResourceName("size"): resource.MustParse("10G"), 231 } 232 patchBytes, err := createPVCPatch(pvc1, pvc2, true /* addResourceVersionCheck */) 233 if err != nil { 234 t.Errorf("error creating patch bytes %v", err) 235 } 236 var patchMap map[string]interface{} 237 err = json.Unmarshal(patchBytes, &patchMap) 238 if err != nil { 239 t.Errorf("error unmarshalling json patch : %v", err) 240 } 241 metadata, ok := patchMap["metadata"].(map[string]interface{}) 242 if !ok { 243 t.Errorf("error converting metadata to version map") 244 } 245 resourceVersion, _ := metadata["resourceVersion"].(string) 246 if resourceVersion != "10" { 247 t.Errorf("expected resource version to 10 got %s", resourceVersion) 248 } 249 } 250 251 type pvcModifier struct { 252 pvc *v1.PersistentVolumeClaim 253 } 254 255 func (m pvcModifier) get() *v1.PersistentVolumeClaim { 256 return m.pvc.DeepCopy() 257 } 258 259 func makePVC(conditions []v1.PersistentVolumeClaimCondition) pvcModifier { 260 pvc := &v1.PersistentVolumeClaim{ 261 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "resize"}, 262 Spec: v1.PersistentVolumeClaimSpec{ 263 AccessModes: []v1.PersistentVolumeAccessMode{ 264 v1.ReadWriteOnce, 265 v1.ReadOnlyMany, 266 }, 267 Resources: v1.VolumeResourceRequirements{ 268 Requests: v1.ResourceList{ 269 v1.ResourceName(v1.ResourceStorage): resource.MustParse("2Gi"), 270 }, 271 }, 272 }, 273 Status: v1.PersistentVolumeClaimStatus{ 274 Phase: v1.ClaimBound, 275 Conditions: conditions, 276 Capacity: v1.ResourceList{ 277 v1.ResourceStorage: resource.MustParse("2Gi"), 278 }, 279 }, 280 } 281 return pvcModifier{pvc} 282 } 283 284 func (m pvcModifier) withStorageResourceStatus(status v1.ClaimResourceStatus) pvcModifier { 285 return m.withResourceStatus(v1.ResourceStorage, status) 286 } 287 288 func (m pvcModifier) withResourceStatus(resource v1.ResourceName, status v1.ClaimResourceStatus) pvcModifier { 289 if m.pvc.Status.AllocatedResourceStatuses != nil && status == "" { 290 delete(m.pvc.Status.AllocatedResourceStatuses, resource) 291 return m 292 } 293 if m.pvc.Status.AllocatedResourceStatuses != nil { 294 m.pvc.Status.AllocatedResourceStatuses[resource] = status 295 } else { 296 m.pvc.Status.AllocatedResourceStatuses = map[v1.ResourceName]v1.ClaimResourceStatus{ 297 resource: status, 298 } 299 } 300 return m 301 }