k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/pv/pv.go (about) 1 /* 2 Copyright 2015 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 pv 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "time" 24 25 "k8s.io/apimachinery/pkg/util/wait" 26 27 "k8s.io/kubernetes/test/e2e/storage/utils" 28 29 "github.com/onsi/ginkgo/v2" 30 v1 "k8s.io/api/core/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 "k8s.io/apimachinery/pkg/api/resource" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/labels" 35 "k8s.io/apimachinery/pkg/types" 36 clientset "k8s.io/client-go/kubernetes" 37 "k8s.io/kubernetes/pkg/volume/util" 38 "k8s.io/kubernetes/test/e2e/framework" 39 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 40 ) 41 42 const ( 43 pdRetryTimeout = 5 * time.Minute 44 pdRetryPollTime = 5 * time.Second 45 46 // VolumeSelectorKey is the key for volume selector. 47 VolumeSelectorKey = "e2e-pv-pool" 48 49 // volumeGidAnnotationKey is the of the annotation on the PersistentVolume 50 // object that specifies a supplemental GID. 51 // it is copied from k8s.io/kubernetes/pkg/volume/util VolumeGidAnnotationKey 52 volumeGidAnnotationKey = "pv.beta.kubernetes.io/gid" 53 ) 54 55 var ( 56 // SELinuxLabel is common selinux labels. 57 SELinuxLabel = &v1.SELinuxOptions{ 58 Level: "s0:c0,c1"} 59 ) 60 61 type pvval struct{} 62 63 // PVMap is a map of all PVs used in the multi pv-pvc tests. The key is the PV's name, which is 64 // guaranteed to be unique. The value is {} (empty struct) since we're only interested 65 // in the PV's name and if it is present. We must always Get the pv object before 66 // referencing any of its values, eg its ClaimRef. 67 type PVMap map[string]pvval 68 69 type pvcval struct{} 70 71 // PVCMap is a map of all PVCs used in the multi pv-pvc tests. The key is "namespace/pvc.Name". The 72 // value is {} (empty struct) since we're only interested in the PVC's name and if it is 73 // present. We must always Get the pvc object before referencing any of its values, eg. 74 // its VolumeName. 75 // Note: It's unsafe to add keys to a map in a loop. Their insertion in the map is 76 // 77 // unpredictable and can result in the same key being iterated over again. 78 type PVCMap map[types.NamespacedName]pvcval 79 80 // PersistentVolumeConfig is consumed by MakePersistentVolume() to generate a PV object 81 // for varying storage options (NFS, ceph, etc.). 82 // (+optional) prebind holds a pre-bound PVC 83 // Example pvSource: 84 // 85 // pvSource: api.PersistentVolumeSource{ 86 // NFS: &api.NFSVolumeSource{ 87 // ... 88 // }, 89 // } 90 type PersistentVolumeConfig struct { 91 // [Optional] NamePrefix defaults to "pv-" if unset 92 NamePrefix string 93 // [Optional] Labels contains information used to organize and categorize 94 // objects 95 Labels labels.Set 96 // [Optional] Annotations contains information used to organize and categorize 97 // objects 98 Annotations map[string]string 99 // PVSource contains the details of the underlying volume and must be set 100 PVSource v1.PersistentVolumeSource 101 // [Optional] Prebind lets you specify a PVC to bind this PV to before 102 // creation 103 Prebind *v1.PersistentVolumeClaim 104 // [Optiona] ReclaimPolicy defaults to "Reclaim" if unset 105 ReclaimPolicy v1.PersistentVolumeReclaimPolicy 106 StorageClassName string 107 // [Optional] NodeAffinity defines constraints that limit what nodes this 108 // volume can be accessed from. 109 NodeAffinity *v1.VolumeNodeAffinity 110 // [Optional] VolumeMode defaults to "Filesystem" if unset 111 VolumeMode *v1.PersistentVolumeMode 112 // [Optional] AccessModes defaults to RWO if unset 113 AccessModes []v1.PersistentVolumeAccessMode 114 // [Optional] Capacity is the storage capacity in Quantity format. Defaults 115 // to "2Gi" if unset 116 Capacity string 117 } 118 119 // PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to 120 // generate a PVC object. 121 type PersistentVolumeClaimConfig struct { 122 // Name of the PVC. If set, overrides NamePrefix 123 Name string 124 // NamePrefix defaults to "pvc-" if unspecified 125 NamePrefix string 126 // ClaimSize must be specified in the Quantity format. Defaults to 2Gi if 127 // unspecified 128 ClaimSize string 129 // AccessModes defaults to RWO if unspecified 130 AccessModes []v1.PersistentVolumeAccessMode 131 Annotations map[string]string 132 Selector *metav1.LabelSelector 133 StorageClassName *string 134 // VolumeMode defaults to nil if unspecified or specified as the empty 135 // string 136 VolumeMode *v1.PersistentVolumeMode 137 } 138 139 // PVPVCCleanup cleans up a pv and pvc in a single pv/pvc test case. 140 // Note: delete errors are appended to []error so that we can attempt to delete both the pvc and pv. 141 func PVPVCCleanup(ctx context.Context, c clientset.Interface, ns string, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) []error { 142 var errs []error 143 144 if pvc != nil { 145 err := DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns) 146 if err != nil { 147 errs = append(errs, fmt.Errorf("failed to delete PVC %q: %w", pvc.Name, err)) 148 } 149 } else { 150 framework.Logf("pvc is nil") 151 } 152 if pv != nil { 153 err := DeletePersistentVolume(ctx, c, pv.Name) 154 if err != nil { 155 errs = append(errs, fmt.Errorf("failed to delete PV %q: %w", pv.Name, err)) 156 } 157 } else { 158 framework.Logf("pv is nil") 159 } 160 return errs 161 } 162 163 // PVPVCMapCleanup Cleans up pvs and pvcs in multi-pv-pvc test cases. Entries found in the pv and claim maps are 164 // deleted as long as the Delete api call succeeds. 165 // Note: delete errors are appended to []error so that as many pvcs and pvs as possible are deleted. 166 func PVPVCMapCleanup(ctx context.Context, c clientset.Interface, ns string, pvols PVMap, claims PVCMap) []error { 167 var errs []error 168 169 for pvcKey := range claims { 170 err := DeletePersistentVolumeClaim(ctx, c, pvcKey.Name, ns) 171 if err != nil { 172 errs = append(errs, fmt.Errorf("failed to delete PVC %q: %w", pvcKey.Name, err)) 173 } else { 174 delete(claims, pvcKey) 175 } 176 } 177 178 for pvKey := range pvols { 179 err := DeletePersistentVolume(ctx, c, pvKey) 180 if err != nil { 181 errs = append(errs, fmt.Errorf("failed to delete PV %q: %w", pvKey, err)) 182 } else { 183 delete(pvols, pvKey) 184 } 185 } 186 return errs 187 } 188 189 // DeletePersistentVolume deletes the PV. 190 func DeletePersistentVolume(ctx context.Context, c clientset.Interface, pvName string) error { 191 if c != nil && len(pvName) > 0 { 192 framework.Logf("Deleting PersistentVolume %q", pvName) 193 err := c.CoreV1().PersistentVolumes().Delete(ctx, pvName, metav1.DeleteOptions{}) 194 if err != nil && !apierrors.IsNotFound(err) { 195 return fmt.Errorf("PV Delete API error: %w", err) 196 } 197 } 198 return nil 199 } 200 201 // DeletePersistentVolumeClaim deletes the Claim. 202 func DeletePersistentVolumeClaim(ctx context.Context, c clientset.Interface, pvcName string, ns string) error { 203 if c != nil && len(pvcName) > 0 { 204 framework.Logf("Deleting PersistentVolumeClaim %q", pvcName) 205 err := c.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName, metav1.DeleteOptions{}) 206 if err != nil && !apierrors.IsNotFound(err) { 207 return fmt.Errorf("PVC Delete API error: %w", err) 208 } 209 } 210 return nil 211 } 212 213 // DeletePVCandValidatePV deletes the PVC and waits for the PV to enter its expected phase. Validate that the PV 214 // has been reclaimed (assumption here about reclaimPolicy). Caller tells this func which 215 // phase value to expect for the pv bound to the to-be-deleted claim. 216 func DeletePVCandValidatePV(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, ns string, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, expectPVPhase v1.PersistentVolumePhase) error { 217 pvname := pvc.Spec.VolumeName 218 framework.Logf("Deleting PVC %v to trigger reclamation of PV %v", pvc.Name, pvname) 219 err := DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns) 220 if err != nil { 221 return err 222 } 223 224 // Wait for the PV's phase to return to be `expectPVPhase` 225 framework.Logf("Waiting for reclaim process to complete.") 226 err = WaitForPersistentVolumePhase(ctx, expectPVPhase, c, pv.Name, framework.Poll, timeouts.PVReclaim) 227 if err != nil { 228 return fmt.Errorf("pv %q phase did not become %v: %w", pv.Name, expectPVPhase, err) 229 } 230 231 // examine the pv's ClaimRef and UID and compare to expected values 232 pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pv.Name, metav1.GetOptions{}) 233 if err != nil { 234 return fmt.Errorf("PV Get API error: %w", err) 235 } 236 cr := pv.Spec.ClaimRef 237 if expectPVPhase == v1.VolumeAvailable { 238 if cr != nil && len(cr.UID) > 0 { 239 return fmt.Errorf("PV is 'Available' but ClaimRef.UID is not empty") 240 } 241 } else if expectPVPhase == v1.VolumeBound { 242 if cr == nil { 243 return fmt.Errorf("PV is 'Bound' but ClaimRef is nil") 244 } 245 if len(cr.UID) == 0 { 246 return fmt.Errorf("PV is 'Bound' but ClaimRef.UID is empty") 247 } 248 } 249 250 framework.Logf("PV %v now in %q phase", pv.Name, expectPVPhase) 251 return nil 252 } 253 254 // DeletePVCandValidatePVGroup wraps deletePVCandValidatePV() by calling the function in a loop over the PV map. Only bound PVs 255 // are deleted. Validates that the claim was deleted and the PV is in the expected Phase (Released, 256 // Available, Bound). 257 // Note: if there are more claims than pvs then some of the remaining claims may bind to just made 258 // 259 // available pvs. 260 func DeletePVCandValidatePVGroup(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, ns string, pvols PVMap, claims PVCMap, expectPVPhase v1.PersistentVolumePhase) error { 261 var boundPVs, deletedPVCs int 262 263 for pvName := range pvols { 264 pv, err := c.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) 265 if err != nil { 266 return fmt.Errorf("PV Get API error: %w", err) 267 } 268 cr := pv.Spec.ClaimRef 269 // if pv is bound then delete the pvc it is bound to 270 if cr != nil && len(cr.Name) > 0 { 271 boundPVs++ 272 // Assert bound PVC is tracked in this test. Failing this might 273 // indicate external PVCs interfering with the test. 274 pvcKey := makePvcKey(ns, cr.Name) 275 if _, found := claims[pvcKey]; !found { 276 return fmt.Errorf("internal: claims map is missing pvc %q", pvcKey) 277 } 278 // get the pvc for the delete call below 279 pvc, err := c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, cr.Name, metav1.GetOptions{}) 280 if err == nil { 281 if err = DeletePVCandValidatePV(ctx, c, timeouts, ns, pvc, pv, expectPVPhase); err != nil { 282 return err 283 } 284 } else if !apierrors.IsNotFound(err) { 285 return fmt.Errorf("PVC Get API error: %w", err) 286 } 287 // delete pvckey from map even if apierrors.IsNotFound above is true and thus the 288 // claim was not actually deleted here 289 delete(claims, pvcKey) 290 deletedPVCs++ 291 } 292 } 293 if boundPVs != deletedPVCs { 294 return fmt.Errorf("expect number of bound PVs (%v) to equal number of deleted PVCs (%v)", boundPVs, deletedPVCs) 295 } 296 return nil 297 } 298 299 // create the PV resource. Fails test on error. 300 func createPV(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { 301 var resultPV *v1.PersistentVolume 302 var lastCreateErr error 303 err := wait.PollUntilContextTimeout(ctx, 29*time.Second, timeouts.PVCreate, true, func(ctx context.Context) (done bool, err error) { 304 resultPV, lastCreateErr = c.CoreV1().PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{}) 305 if lastCreateErr != nil { 306 // If we hit a quota problem, we are not done and should retry again. This happens to be the quota failure string for GCP. 307 // If quota failure strings are found for other platforms, they can be added to improve reliability when running 308 // many parallel test jobs in a single cloud account. This corresponds to controller-like behavior and 309 // to what we would recommend for general clients. 310 if strings.Contains(lastCreateErr.Error(), `googleapi: Error 403: Quota exceeded for quota group`) { 311 return false, nil 312 } 313 314 // if it was not a quota failure, fail immediately 315 return false, lastCreateErr 316 } 317 318 return true, nil 319 }) 320 // if we have an error from creating the PV, use that instead of a timeout error 321 if lastCreateErr != nil { 322 return nil, fmt.Errorf("PV Create API error: %w", err) 323 } 324 if err != nil { 325 return nil, fmt.Errorf("PV Create API error: %w", err) 326 } 327 328 return resultPV, nil 329 } 330 331 // CreatePV creates the PV resource. Fails test on error. 332 func CreatePV(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { 333 return createPV(ctx, c, timeouts, pv) 334 } 335 336 // CreatePVC creates the PVC resource. Fails test on error. 337 func CreatePVC(ctx context.Context, c clientset.Interface, ns string, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaim, error) { 338 pvc, err := c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{}) 339 if err != nil { 340 return nil, fmt.Errorf("PVC Create API error: %w", err) 341 } 342 return pvc, nil 343 } 344 345 // CreatePVCPV creates a PVC followed by the PV based on the passed in nfs-server ip and 346 // namespace. If the "preBind" bool is true then pre-bind the PV to the PVC 347 // via the PV's ClaimRef. Return the pv and pvc to reflect the created objects. 348 // Note: in the pre-bind case the real PVC name, which is generated, is not 349 // 350 // known until after the PVC is instantiated. This is why the pvc is created 351 // before the pv. 352 func CreatePVCPV(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, pvConfig PersistentVolumeConfig, pvcConfig PersistentVolumeClaimConfig, ns string, preBind bool) (*v1.PersistentVolume, *v1.PersistentVolumeClaim, error) { 353 // make the pvc spec 354 pvc := MakePersistentVolumeClaim(pvcConfig, ns) 355 preBindMsg := "" 356 if preBind { 357 preBindMsg = " pre-bound" 358 pvConfig.Prebind = pvc 359 } 360 // make the pv spec 361 pv := MakePersistentVolume(pvConfig) 362 363 ginkgo.By(fmt.Sprintf("Creating a PVC followed by a%s PV", preBindMsg)) 364 pvc, err := CreatePVC(ctx, c, ns, pvc) 365 if err != nil { 366 return nil, nil, err 367 } 368 369 // instantiate the pv, handle pre-binding by ClaimRef if needed 370 if preBind { 371 pv.Spec.ClaimRef.Name = pvc.Name 372 } 373 pv, err = createPV(ctx, c, timeouts, pv) 374 if err != nil { 375 return nil, pvc, err 376 } 377 return pv, pvc, nil 378 } 379 380 // CreatePVPVC creates a PV followed by the PVC based on the passed in nfs-server ip and 381 // namespace. If the "preBind" bool is true then pre-bind the PVC to the PV 382 // via the PVC's VolumeName. Return the pv and pvc to reflect the created 383 // objects. 384 // Note: in the pre-bind case the real PV name, which is generated, is not 385 // 386 // known until after the PV is instantiated. This is why the pv is created 387 // before the pvc. 388 func CreatePVPVC(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, pvConfig PersistentVolumeConfig, pvcConfig PersistentVolumeClaimConfig, ns string, preBind bool) (*v1.PersistentVolume, *v1.PersistentVolumeClaim, error) { 389 preBindMsg := "" 390 if preBind { 391 preBindMsg = " pre-bound" 392 } 393 framework.Logf("Creating a PV followed by a%s PVC", preBindMsg) 394 395 // make the pv and pvc definitions 396 pv := MakePersistentVolume(pvConfig) 397 pvc := MakePersistentVolumeClaim(pvcConfig, ns) 398 399 // instantiate the pv 400 pv, err := createPV(ctx, c, timeouts, pv) 401 if err != nil { 402 return nil, nil, err 403 } 404 // instantiate the pvc, handle pre-binding by VolumeName if needed 405 if preBind { 406 pvc.Spec.VolumeName = pv.Name 407 } 408 pvc, err = CreatePVC(ctx, c, ns, pvc) 409 if err != nil { 410 return pv, nil, err 411 } 412 return pv, pvc, nil 413 } 414 415 // CreatePVsPVCs creates the desired number of PVs and PVCs and returns them in separate maps. If the 416 // number of PVs != the number of PVCs then the min of those two counts is the number of 417 // PVs expected to bind. If a Create error occurs, the returned maps may contain pv and pvc 418 // entries for the resources that were successfully created. In other words, when the caller 419 // sees an error returned, it needs to decide what to do about entries in the maps. 420 // Note: when the test suite deletes the namespace orphaned pvcs and pods are deleted. However, 421 // 422 // orphaned pvs are not deleted and will remain after the suite completes. 423 func CreatePVsPVCs(ctx context.Context, numpvs, numpvcs int, c clientset.Interface, timeouts *framework.TimeoutContext, ns string, pvConfig PersistentVolumeConfig, pvcConfig PersistentVolumeClaimConfig) (PVMap, PVCMap, error) { 424 pvMap := make(PVMap, numpvs) 425 pvcMap := make(PVCMap, numpvcs) 426 extraPVCs := 0 427 extraPVs := numpvs - numpvcs 428 if extraPVs < 0 { 429 extraPVCs = -extraPVs 430 extraPVs = 0 431 } 432 pvsToCreate := numpvs - extraPVs // want the min(numpvs, numpvcs) 433 434 // create pvs and pvcs 435 for i := 0; i < pvsToCreate; i++ { 436 pv, pvc, err := CreatePVPVC(ctx, c, timeouts, pvConfig, pvcConfig, ns, false) 437 if err != nil { 438 return pvMap, pvcMap, err 439 } 440 pvMap[pv.Name] = pvval{} 441 pvcMap[makePvcKey(ns, pvc.Name)] = pvcval{} 442 } 443 444 // create extra pvs or pvcs as needed 445 for i := 0; i < extraPVs; i++ { 446 pv := MakePersistentVolume(pvConfig) 447 pv, err := createPV(ctx, c, timeouts, pv) 448 if err != nil { 449 return pvMap, pvcMap, err 450 } 451 pvMap[pv.Name] = pvval{} 452 } 453 for i := 0; i < extraPVCs; i++ { 454 pvc := MakePersistentVolumeClaim(pvcConfig, ns) 455 pvc, err := CreatePVC(ctx, c, ns, pvc) 456 if err != nil { 457 return pvMap, pvcMap, err 458 } 459 pvcMap[makePvcKey(ns, pvc.Name)] = pvcval{} 460 } 461 return pvMap, pvcMap, nil 462 } 463 464 // WaitOnPVandPVC waits for the pv and pvc to bind to each other. 465 func WaitOnPVandPVC(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, ns string, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) error { 466 // Wait for newly created PVC to bind to the PV 467 framework.Logf("Waiting for PV %v to bind to PVC %v", pv.Name, pvc.Name) 468 err := WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, pvc.Name, framework.Poll, timeouts.ClaimBound) 469 if err != nil { 470 return fmt.Errorf("PVC %q did not become Bound: %w", pvc.Name, err) 471 } 472 473 // Wait for PersistentVolume.Status.Phase to be Bound, which it should be 474 // since the PVC is already bound. 475 err = WaitForPersistentVolumePhase(ctx, v1.VolumeBound, c, pv.Name, framework.Poll, timeouts.PVBound) 476 if err != nil { 477 return fmt.Errorf("PV %q did not become Bound: %w", pv.Name, err) 478 } 479 480 // Re-get the pv and pvc objects 481 pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pv.Name, metav1.GetOptions{}) 482 if err != nil { 483 return fmt.Errorf("PV Get API error: %w", err) 484 } 485 pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvc.Name, metav1.GetOptions{}) 486 if err != nil { 487 return fmt.Errorf("PVC Get API error: %w", err) 488 } 489 490 // The pv and pvc are both bound, but to each other? 491 // Check that the PersistentVolume.ClaimRef matches the PVC 492 if pv.Spec.ClaimRef == nil { 493 return fmt.Errorf("PV %q ClaimRef is nil", pv.Name) 494 } 495 if pv.Spec.ClaimRef.Name != pvc.Name { 496 return fmt.Errorf("PV %q ClaimRef's name (%q) should be %q", pv.Name, pv.Spec.ClaimRef.Name, pvc.Name) 497 } 498 if pvc.Spec.VolumeName != pv.Name { 499 return fmt.Errorf("PVC %q VolumeName (%q) should be %q", pvc.Name, pvc.Spec.VolumeName, pv.Name) 500 } 501 if pv.Spec.ClaimRef.UID != pvc.UID { 502 return fmt.Errorf("PV %q ClaimRef's UID (%q) should be %q", pv.Name, pv.Spec.ClaimRef.UID, pvc.UID) 503 } 504 return nil 505 } 506 507 // WaitAndVerifyBinds searches for bound PVs and PVCs by examining pvols for non-nil claimRefs. 508 // NOTE: Each iteration waits for a maximum of 3 minutes per PV and, if the PV is bound, 509 // 510 // up to 3 minutes for the PVC. When the number of PVs != number of PVCs, this can lead 511 // to situations where the maximum wait times are reached several times in succession, 512 // extending test time. Thus, it is recommended to keep the delta between PVs and PVCs 513 // small. 514 func WaitAndVerifyBinds(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, ns string, pvols PVMap, claims PVCMap, testExpected bool) error { 515 var actualBinds int 516 expectedBinds := len(pvols) 517 if expectedBinds > len(claims) { // want the min of # pvs or #pvcs 518 expectedBinds = len(claims) 519 } 520 521 for pvName := range pvols { 522 err := WaitForPersistentVolumePhase(ctx, v1.VolumeBound, c, pvName, framework.Poll, timeouts.PVBound) 523 if err != nil && len(pvols) > len(claims) { 524 framework.Logf("WARN: pv %v is not bound after max wait", pvName) 525 framework.Logf(" This may be ok since there are more pvs than pvcs") 526 continue 527 } 528 if err != nil { 529 return fmt.Errorf("PV %q did not become Bound: %w", pvName, err) 530 } 531 532 pv, err := c.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) 533 if err != nil { 534 return fmt.Errorf("PV Get API error: %w", err) 535 } 536 cr := pv.Spec.ClaimRef 537 if cr != nil && len(cr.Name) > 0 { 538 // Assert bound pvc is a test resource. Failing assertion could 539 // indicate non-test PVC interference or a bug in the test 540 pvcKey := makePvcKey(ns, cr.Name) 541 if _, found := claims[pvcKey]; !found { 542 return fmt.Errorf("internal: claims map is missing pvc %q", pvcKey) 543 } 544 545 err := WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, cr.Name, framework.Poll, timeouts.ClaimBound) 546 if err != nil { 547 return fmt.Errorf("PVC %q did not become Bound: %w", cr.Name, err) 548 } 549 actualBinds++ 550 } 551 } 552 553 if testExpected && actualBinds != expectedBinds { 554 return fmt.Errorf("expect number of bound PVs (%v) to equal number of claims (%v)", actualBinds, expectedBinds) 555 } 556 return nil 557 } 558 559 // Return a pvckey struct. 560 func makePvcKey(ns, name string) types.NamespacedName { 561 return types.NamespacedName{Namespace: ns, Name: name} 562 } 563 564 // MakePersistentVolume returns a PV definition based on the nfs server IP. If the PVC is not nil 565 // then the PV is defined with a ClaimRef which includes the PVC's namespace. 566 // If the PVC is nil then the PV is not defined with a ClaimRef. If no reclaimPolicy 567 // is assigned, assumes "Retain". Specs are expected to match the test's PVC. 568 // Note: the passed-in claim does not have a name until it is created and thus the PV's 569 // 570 // ClaimRef cannot be completely filled-in in this func. Therefore, the ClaimRef's name 571 // is added later in CreatePVCPV. 572 func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume { 573 var claimRef *v1.ObjectReference 574 575 if len(pvConfig.AccessModes) == 0 { 576 pvConfig.AccessModes = append(pvConfig.AccessModes, v1.ReadWriteOnce) 577 } 578 579 if len(pvConfig.NamePrefix) == 0 { 580 pvConfig.NamePrefix = "pv-" 581 } 582 583 if pvConfig.ReclaimPolicy == "" { 584 pvConfig.ReclaimPolicy = v1.PersistentVolumeReclaimRetain 585 } 586 587 if len(pvConfig.Capacity) == 0 { 588 pvConfig.Capacity = "2Gi" 589 } 590 591 if pvConfig.Prebind != nil { 592 claimRef = &v1.ObjectReference{ 593 Kind: "PersistentVolumeClaim", 594 APIVersion: "v1", 595 Name: pvConfig.Prebind.Name, 596 Namespace: pvConfig.Prebind.Namespace, 597 UID: pvConfig.Prebind.UID, 598 } 599 } 600 601 annotations := map[string]string{ 602 volumeGidAnnotationKey: "777", 603 } 604 for k, v := range pvConfig.Annotations { 605 annotations[k] = v 606 } 607 608 return &v1.PersistentVolume{ 609 ObjectMeta: metav1.ObjectMeta{ 610 GenerateName: pvConfig.NamePrefix, 611 Labels: pvConfig.Labels, 612 Annotations: annotations, 613 }, 614 Spec: v1.PersistentVolumeSpec{ 615 PersistentVolumeReclaimPolicy: pvConfig.ReclaimPolicy, 616 Capacity: v1.ResourceList{ 617 v1.ResourceStorage: resource.MustParse(pvConfig.Capacity), 618 }, 619 PersistentVolumeSource: pvConfig.PVSource, 620 AccessModes: pvConfig.AccessModes, 621 ClaimRef: claimRef, 622 StorageClassName: pvConfig.StorageClassName, 623 NodeAffinity: pvConfig.NodeAffinity, 624 VolumeMode: pvConfig.VolumeMode, 625 }, 626 } 627 } 628 629 // MakePersistentVolumeClaim returns a PVC API Object based on the PersistentVolumeClaimConfig. 630 func MakePersistentVolumeClaim(cfg PersistentVolumeClaimConfig, ns string) *v1.PersistentVolumeClaim { 631 632 if len(cfg.AccessModes) == 0 { 633 cfg.AccessModes = append(cfg.AccessModes, v1.ReadWriteOnce) 634 } 635 636 if len(cfg.ClaimSize) == 0 { 637 cfg.ClaimSize = "2Gi" 638 } 639 640 if len(cfg.NamePrefix) == 0 { 641 cfg.NamePrefix = "pvc-" 642 } 643 644 if cfg.VolumeMode != nil && *cfg.VolumeMode == "" { 645 framework.Logf("Warning: Making PVC: VolumeMode specified as invalid empty string, treating as nil") 646 cfg.VolumeMode = nil 647 } 648 649 return &v1.PersistentVolumeClaim{ 650 ObjectMeta: metav1.ObjectMeta{ 651 Name: cfg.Name, 652 GenerateName: cfg.NamePrefix, 653 Namespace: ns, 654 Annotations: cfg.Annotations, 655 }, 656 Spec: v1.PersistentVolumeClaimSpec{ 657 Selector: cfg.Selector, 658 AccessModes: cfg.AccessModes, 659 Resources: v1.VolumeResourceRequirements{ 660 Requests: v1.ResourceList{ 661 v1.ResourceStorage: resource.MustParse(cfg.ClaimSize), 662 }, 663 }, 664 StorageClassName: cfg.StorageClassName, 665 VolumeMode: cfg.VolumeMode, 666 }, 667 } 668 } 669 670 func createPDWithRetry(ctx context.Context, zone string) (string, error) { 671 var err error 672 var newDiskName string 673 for start := time.Now(); ; time.Sleep(pdRetryPollTime) { 674 if time.Since(start) >= pdRetryTimeout || 675 ctx.Err() != nil { 676 return "", fmt.Errorf("timed out while trying to create PD in zone %q, last error: %w", zone, err) 677 } 678 679 newDiskName, err = createPD(zone) 680 if err != nil { 681 framework.Logf("Couldn't create a new PD in zone %q, sleeping 5 seconds: %v", zone, err) 682 continue 683 } 684 framework.Logf("Successfully created a new PD in zone %q: %q.", zone, newDiskName) 685 return newDiskName, nil 686 } 687 } 688 689 func CreateShare() (string, string, string, error) { 690 return framework.TestContext.CloudConfig.Provider.CreateShare() 691 } 692 693 func DeleteShare(accountName, shareName string) error { 694 return framework.TestContext.CloudConfig.Provider.DeleteShare(accountName, shareName) 695 } 696 697 // CreatePDWithRetry creates PD with retry. 698 func CreatePDWithRetry(ctx context.Context) (string, error) { 699 return createPDWithRetry(ctx, "") 700 } 701 702 // CreatePDWithRetryAndZone creates PD on zone with retry. 703 func CreatePDWithRetryAndZone(ctx context.Context, zone string) (string, error) { 704 return createPDWithRetry(ctx, zone) 705 } 706 707 // DeletePDWithRetry deletes PD with retry. 708 func DeletePDWithRetry(ctx context.Context, diskName string) error { 709 var err error 710 for start := time.Now(); ; time.Sleep(pdRetryPollTime) { 711 if time.Since(start) >= pdRetryTimeout || 712 ctx.Err() != nil { 713 return fmt.Errorf("timed out while trying to delete PD %q, last error: %w", diskName, err) 714 } 715 err = deletePD(diskName) 716 if err != nil { 717 framework.Logf("Couldn't delete PD %q, sleeping %v: %v", diskName, pdRetryPollTime, err) 718 continue 719 } 720 framework.Logf("Successfully deleted PD %q.", diskName) 721 return nil 722 } 723 } 724 725 func createPD(zone string) (string, error) { 726 if zone == "" { 727 zone = framework.TestContext.CloudConfig.Zone 728 } 729 return framework.TestContext.CloudConfig.Provider.CreatePD(zone) 730 } 731 732 func deletePD(pdName string) error { 733 return framework.TestContext.CloudConfig.Provider.DeletePD(pdName) 734 } 735 736 // WaitForPVClaimBoundPhase waits until all pvcs phase set to bound 737 func WaitForPVClaimBoundPhase(ctx context.Context, client clientset.Interface, pvclaims []*v1.PersistentVolumeClaim, timeout time.Duration) ([]*v1.PersistentVolume, error) { 738 persistentvolumes := make([]*v1.PersistentVolume, len(pvclaims)) 739 740 for index, claim := range pvclaims { 741 err := WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, claim.Namespace, claim.Name, framework.Poll, timeout) 742 if err != nil { 743 return persistentvolumes, err 744 } 745 // Get new copy of the claim 746 claim, err = client.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(ctx, claim.Name, metav1.GetOptions{}) 747 if err != nil { 748 return persistentvolumes, fmt.Errorf("PVC Get API error: %w", err) 749 } 750 // Get the bounded PV 751 persistentvolumes[index], err = client.CoreV1().PersistentVolumes().Get(ctx, claim.Spec.VolumeName, metav1.GetOptions{}) 752 if err != nil { 753 return persistentvolumes, fmt.Errorf("PV Get API error: %w", err) 754 } 755 } 756 return persistentvolumes, nil 757 } 758 759 // WaitForPersistentVolumePhase waits for a PersistentVolume to be in a specific phase or until timeout occurs, whichever comes first. 760 func WaitForPersistentVolumePhase(ctx context.Context, phase v1.PersistentVolumePhase, c clientset.Interface, pvName string, poll, timeout time.Duration) error { 761 framework.Logf("Waiting up to %v for PersistentVolume %s to have phase %s", timeout, pvName, phase) 762 for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) { 763 pv, err := c.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) 764 if err != nil { 765 framework.Logf("Get persistent volume %s in failed, ignoring for %v: %v", pvName, poll, err) 766 continue 767 } 768 if pv.Status.Phase == phase { 769 framework.Logf("PersistentVolume %s found and phase=%s (%v)", pvName, phase, time.Since(start)) 770 return nil 771 } 772 framework.Logf("PersistentVolume %s found but phase is %s instead of %s.", pvName, pv.Status.Phase, phase) 773 } 774 return fmt.Errorf("PersistentVolume %s not in phase %s within %v", pvName, phase, timeout) 775 } 776 777 // WaitForPersistentVolumeClaimPhase waits for a PersistentVolumeClaim to be in a specific phase or until timeout occurs, whichever comes first. 778 func WaitForPersistentVolumeClaimPhase(ctx context.Context, phase v1.PersistentVolumeClaimPhase, c clientset.Interface, ns string, pvcName string, poll, timeout time.Duration) error { 779 return WaitForPersistentVolumeClaimsPhase(ctx, phase, c, ns, []string{pvcName}, poll, timeout, true) 780 } 781 782 // WaitForPersistentVolumeClaimsPhase waits for any (if matchAny is true) or all (if matchAny is false) PersistentVolumeClaims 783 // to be in a specific phase or until timeout occurs, whichever comes first. 784 func WaitForPersistentVolumeClaimsPhase(ctx context.Context, phase v1.PersistentVolumeClaimPhase, c clientset.Interface, ns string, pvcNames []string, poll, timeout time.Duration, matchAny bool) error { 785 if len(pvcNames) == 0 { 786 return fmt.Errorf("Incorrect parameter: Need at least one PVC to track. Found 0") 787 } 788 framework.Logf("Waiting up to timeout=%v for PersistentVolumeClaims %v to have phase %s", timeout, pvcNames, phase) 789 for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) { 790 phaseFoundInAllClaims := true 791 for _, pvcName := range pvcNames { 792 pvc, err := c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvcName, metav1.GetOptions{}) 793 if err != nil { 794 framework.Logf("Failed to get claim %q, retrying in %v. Error: %v", pvcName, poll, err) 795 phaseFoundInAllClaims = false 796 break 797 } 798 if pvc.Status.Phase == phase { 799 framework.Logf("PersistentVolumeClaim %s found and phase=%s (%v)", pvcName, phase, time.Since(start)) 800 if matchAny { 801 return nil 802 } 803 } else { 804 framework.Logf("PersistentVolumeClaim %s found but phase is %s instead of %s.", pvcName, pvc.Status.Phase, phase) 805 phaseFoundInAllClaims = false 806 } 807 } 808 if phaseFoundInAllClaims { 809 return nil 810 } 811 } 812 return fmt.Errorf("PersistentVolumeClaims %v not all in phase %s within %v", pvcNames, phase, timeout) 813 } 814 815 // CreatePVSource creates a PV source. 816 func CreatePVSource(ctx context.Context, zone string) (*v1.PersistentVolumeSource, error) { 817 diskName, err := CreatePDWithRetryAndZone(ctx, zone) 818 if err != nil { 819 return nil, err 820 } 821 return framework.TestContext.CloudConfig.Provider.CreatePVSource(ctx, zone, diskName) 822 } 823 824 // DeletePVSource deletes a PV source. 825 func DeletePVSource(ctx context.Context, pvSource *v1.PersistentVolumeSource) error { 826 return framework.TestContext.CloudConfig.Provider.DeletePVSource(ctx, pvSource) 827 } 828 829 // GetDefaultStorageClassName returns default storageClass or return error 830 func GetDefaultStorageClassName(ctx context.Context, c clientset.Interface) (string, error) { 831 list, err := c.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{}) 832 if err != nil { 833 return "", fmt.Errorf("Error listing storage classes: %w", err) 834 } 835 var scName string 836 for _, sc := range list.Items { 837 if util.IsDefaultAnnotation(sc.ObjectMeta) { 838 if len(scName) != 0 { 839 return "", fmt.Errorf("Multiple default storage classes found: %q and %q", scName, sc.Name) 840 } 841 scName = sc.Name 842 } 843 } 844 if len(scName) == 0 { 845 return "", fmt.Errorf("No default storage class found") 846 } 847 framework.Logf("Default storage class: %q", scName) 848 return scName, nil 849 } 850 851 // SkipIfNoDefaultStorageClass skips tests if no default SC can be found. 852 func SkipIfNoDefaultStorageClass(ctx context.Context, c clientset.Interface) { 853 _, err := GetDefaultStorageClassName(ctx, c) 854 if err != nil { 855 e2eskipper.Skipf("error finding default storageClass : %v", err) 856 } 857 } 858 859 // WaitForPersistentVolumeDeleted waits for a PersistentVolume to get deleted or until timeout occurs, whichever comes first. 860 func WaitForPersistentVolumeDeleted(ctx context.Context, c clientset.Interface, pvName string, poll, timeout time.Duration) error { 861 framework.Logf("Waiting up to %v for PersistentVolume %s to get deleted", timeout, pvName) 862 for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) { 863 pv, err := c.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) 864 if err == nil { 865 framework.Logf("PersistentVolume %s found and phase=%s (%v)", pvName, pv.Status.Phase, time.Since(start)) 866 continue 867 } 868 if apierrors.IsNotFound(err) { 869 framework.Logf("PersistentVolume %s was removed", pvName) 870 return nil 871 } 872 framework.Logf("Get persistent volume %s in failed, ignoring for %v: %v", pvName, poll, err) 873 } 874 return fmt.Errorf("PersistentVolume %s still exists within %v", pvName, timeout) 875 } 876 877 // WaitForPVCFinalizer waits for a finalizer to be added to a PVC in a given namespace. 878 func WaitForPVCFinalizer(ctx context.Context, cs clientset.Interface, name, namespace, finalizer string, poll, timeout time.Duration) error { 879 var ( 880 err error 881 pvc *v1.PersistentVolumeClaim 882 ) 883 framework.Logf("Waiting up to %v for PersistentVolumeClaim %s/%s to contain finalizer %s", timeout, namespace, name, finalizer) 884 if successful := utils.WaitUntil(poll, timeout, func() bool { 885 pvc, err = cs.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{}) 886 if err != nil { 887 framework.Logf("Failed to get PersistentVolumeClaim %s/%s with err: %v. Will retry in %v", name, namespace, err, timeout) 888 return false 889 } 890 for _, f := range pvc.Finalizers { 891 if f == finalizer { 892 return true 893 } 894 } 895 return false 896 }); successful { 897 return nil 898 } 899 if err == nil { 900 err = fmt.Errorf("finalizer %s not added to pvc %s/%s", finalizer, namespace, name) 901 } 902 return err 903 } 904 905 // GetDefaultFSType returns the default fsType 906 func GetDefaultFSType() string { 907 if framework.NodeOSDistroIs("windows") { 908 return "ntfs" 909 } 910 return "ext4" 911 }