k8s.io/kubernetes@v1.29.3/test/e2e/storage/framework/volume_resource.go (about)

     1  /*
     2  Copyright 2020 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 framework
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	v1 "k8s.io/api/core/v1"
    26  	storagev1 "k8s.io/api/storage/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    31  	"k8s.io/kubernetes/test/e2e/framework"
    32  	e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
    33  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    34  	e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
    35  	storageutils "k8s.io/kubernetes/test/e2e/storage/utils"
    36  )
    37  
    38  // VolumeResource is a generic implementation of TestResource that wil be able to
    39  // be used in most of TestSuites.
    40  // See volume_io.go or volumes.go in test/e2e/storage/testsuites/ for how to use this resource.
    41  // Also, see subpath.go in the same directory for how to extend and use it.
    42  type VolumeResource struct {
    43  	Config    *PerTestConfig
    44  	Pattern   TestPattern
    45  	VolSource *v1.VolumeSource
    46  	Pvc       *v1.PersistentVolumeClaim
    47  	Pv        *v1.PersistentVolume
    48  	Sc        *storagev1.StorageClass
    49  
    50  	Volume TestVolume
    51  }
    52  
    53  // CreateVolumeResource constructs a VolumeResource for the current test. It knows how to deal with
    54  // different test pattern volume types.
    55  func CreateVolumeResource(ctx context.Context, driver TestDriver, config *PerTestConfig, pattern TestPattern, testVolumeSizeRange e2evolume.SizeRange) *VolumeResource {
    56  	return CreateVolumeResourceWithAccessModes(ctx, driver, config, pattern, testVolumeSizeRange, driver.GetDriverInfo().RequiredAccessModes)
    57  }
    58  
    59  // CreateVolumeResourceWithAccessModes constructs a VolumeResource for the current test with the provided access modes.
    60  func CreateVolumeResourceWithAccessModes(ctx context.Context, driver TestDriver, config *PerTestConfig, pattern TestPattern, testVolumeSizeRange e2evolume.SizeRange, accessModes []v1.PersistentVolumeAccessMode) *VolumeResource {
    61  	r := VolumeResource{
    62  		Config:  config,
    63  		Pattern: pattern,
    64  	}
    65  	dInfo := driver.GetDriverInfo()
    66  	f := config.Framework
    67  	cs := f.ClientSet
    68  
    69  	// Create volume for pre-provisioned volume tests
    70  	r.Volume = CreateVolume(ctx, driver, config, pattern.VolType)
    71  
    72  	switch pattern.VolType {
    73  	case InlineVolume:
    74  		framework.Logf("Creating resource for inline volume")
    75  		if iDriver, ok := driver.(InlineVolumeTestDriver); ok {
    76  			r.VolSource = iDriver.GetVolumeSource(false, pattern.FsType, r.Volume)
    77  		}
    78  	case PreprovisionedPV:
    79  		framework.Logf("Creating resource for pre-provisioned PV")
    80  		if pDriver, ok := driver.(PreprovisionedPVTestDriver); ok {
    81  			pvSource, volumeNodeAffinity := pDriver.GetPersistentVolumeSource(false, pattern.FsType, r.Volume)
    82  			if pvSource != nil {
    83  				r.Pv, r.Pvc = createPVCPV(ctx, f, dInfo.Name, pvSource, volumeNodeAffinity, pattern.VolMode, accessModes)
    84  				r.VolSource = storageutils.CreateVolumeSource(r.Pvc.Name, false /* readOnly */)
    85  			}
    86  		}
    87  	case DynamicPV, GenericEphemeralVolume:
    88  		framework.Logf("Creating resource for dynamic PV")
    89  		if dDriver, ok := driver.(DynamicPVTestDriver); ok {
    90  			var err error
    91  			driverVolumeSizeRange := dDriver.GetDriverInfo().SupportedSizeRange
    92  			claimSize, err := storageutils.GetSizeRangesIntersection(testVolumeSizeRange, driverVolumeSizeRange)
    93  			framework.ExpectNoError(err, "determine intersection of test size range %+v and driver size range %+v", testVolumeSizeRange, driverVolumeSizeRange)
    94  			framework.Logf("Using claimSize:%s, test suite supported size:%v, driver(%s) supported size:%v ", claimSize, testVolumeSizeRange, dDriver.GetDriverInfo().Name, testVolumeSizeRange)
    95  			r.Sc = dDriver.GetDynamicProvisionStorageClass(ctx, r.Config, pattern.FsType)
    96  
    97  			if pattern.BindingMode != "" {
    98  				r.Sc.VolumeBindingMode = &pattern.BindingMode
    99  			}
   100  			r.Sc.AllowVolumeExpansion = &pattern.AllowExpansion
   101  
   102  			ginkgo.By("creating a StorageClass " + r.Sc.Name)
   103  
   104  			r.Sc, err = cs.StorageV1().StorageClasses().Create(ctx, r.Sc, metav1.CreateOptions{})
   105  			framework.ExpectNoError(err)
   106  
   107  			switch pattern.VolType {
   108  			case DynamicPV:
   109  				r.Pv, r.Pvc = createPVCPVFromDynamicProvisionSC(
   110  					ctx, f, dInfo.Name, claimSize, r.Sc, pattern.VolMode, accessModes)
   111  				r.VolSource = storageutils.CreateVolumeSource(r.Pvc.Name, false /* readOnly */)
   112  			case GenericEphemeralVolume:
   113  				driverVolumeSizeRange := dDriver.GetDriverInfo().SupportedSizeRange
   114  				claimSize, err := storageutils.GetSizeRangesIntersection(testVolumeSizeRange, driverVolumeSizeRange)
   115  				framework.ExpectNoError(err, "determine intersection of test size range %+v and driver size range %+v", testVolumeSizeRange, driverVolumeSizeRange)
   116  				r.VolSource = createEphemeralVolumeSource(r.Sc.Name, pattern.VolMode, accessModes, claimSize)
   117  			}
   118  		}
   119  	case CSIInlineVolume:
   120  		framework.Logf("Creating resource for CSI ephemeral inline volume")
   121  		if eDriver, ok := driver.(EphemeralTestDriver); ok {
   122  			attributes, _, _ := eDriver.GetVolume(config, 0)
   123  			r.VolSource = &v1.VolumeSource{
   124  				CSI: &v1.CSIVolumeSource{
   125  					Driver:           eDriver.GetCSIDriverName(config),
   126  					VolumeAttributes: attributes,
   127  				},
   128  			}
   129  			if pattern.FsType != "" {
   130  				r.VolSource.CSI.FSType = &pattern.FsType
   131  			}
   132  		}
   133  	default:
   134  		framework.Failf("VolumeResource doesn't support: %s", pattern.VolType)
   135  	}
   136  
   137  	if r.VolSource == nil {
   138  		e2eskipper.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, pattern.VolType)
   139  	}
   140  
   141  	return &r
   142  }
   143  
   144  func createEphemeralVolumeSource(scName string, volMode v1.PersistentVolumeMode, accessModes []v1.PersistentVolumeAccessMode, claimSize string) *v1.VolumeSource {
   145  	if len(accessModes) == 0 {
   146  		accessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   147  	}
   148  	if volMode == "" {
   149  		volMode = v1.PersistentVolumeFilesystem
   150  	}
   151  	return &v1.VolumeSource{
   152  		Ephemeral: &v1.EphemeralVolumeSource{
   153  			VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
   154  				Spec: v1.PersistentVolumeClaimSpec{
   155  					StorageClassName: &scName,
   156  					AccessModes:      accessModes,
   157  					VolumeMode:       &volMode,
   158  					Resources: v1.VolumeResourceRequirements{
   159  						Requests: v1.ResourceList{
   160  							v1.ResourceStorage: resource.MustParse(claimSize),
   161  						},
   162  					},
   163  				},
   164  			},
   165  		},
   166  	}
   167  }
   168  
   169  // CleanupResource cleans up VolumeResource
   170  func (r *VolumeResource) CleanupResource(ctx context.Context) error {
   171  	f := r.Config.Framework
   172  	var cleanUpErrs []error
   173  	if r.Pvc != nil || r.Pv != nil {
   174  		switch r.Pattern.VolType {
   175  		case PreprovisionedPV:
   176  			ginkgo.By("Deleting pv and pvc")
   177  			if errs := e2epv.PVPVCCleanup(ctx, f.ClientSet, f.Namespace.Name, r.Pv, r.Pvc); len(errs) != 0 {
   178  				framework.Failf("Failed to delete PVC or PV: %v", utilerrors.NewAggregate(errs))
   179  			}
   180  		case DynamicPV:
   181  			ginkgo.By("Deleting pvc")
   182  			// We only delete the PVC so that PV (and disk) can be cleaned up by dynamic provisioner
   183  			if r.Pv != nil && r.Pv.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimDelete {
   184  				framework.Failf("Test framework does not currently support Dynamically Provisioned Persistent Volume %v specified with reclaim policy that isn't %v",
   185  					r.Pv.Name, v1.PersistentVolumeReclaimDelete)
   186  			}
   187  			if r.Pvc != nil {
   188  				cs := f.ClientSet
   189  				pv := r.Pv
   190  				if pv == nil && r.Pvc.Name != "" {
   191  					// This happens for late binding. Check whether we have a volume now that we need to wait for.
   192  					pvc, err := cs.CoreV1().PersistentVolumeClaims(r.Pvc.Namespace).Get(ctx, r.Pvc.Name, metav1.GetOptions{})
   193  					switch {
   194  					case err == nil:
   195  						if pvc.Spec.VolumeName != "" {
   196  							pv, err = cs.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
   197  							if err != nil {
   198  								cleanUpErrs = append(cleanUpErrs, fmt.Errorf("failed to find PV %v: %w", pvc.Spec.VolumeName, err))
   199  							}
   200  						}
   201  					case apierrors.IsNotFound(err):
   202  						// Without the PVC, we cannot locate the corresponding PV. Let's
   203  						// hope that it is gone.
   204  					default:
   205  						cleanUpErrs = append(cleanUpErrs, fmt.Errorf("failed to find PVC %v: %w", r.Pvc.Name, err))
   206  					}
   207  				}
   208  
   209  				err := e2epv.DeletePersistentVolumeClaim(ctx, f.ClientSet, r.Pvc.Name, f.Namespace.Name)
   210  				if err != nil {
   211  					cleanUpErrs = append(cleanUpErrs, fmt.Errorf("failed to delete PVC %v: %w", r.Pvc.Name, err))
   212  				}
   213  
   214  				if pv != nil {
   215  					err = e2epv.WaitForPersistentVolumeDeleted(ctx, f.ClientSet, pv.Name, 5*time.Second, f.Timeouts.PVDelete)
   216  					if err != nil {
   217  						cleanUpErrs = append(cleanUpErrs, fmt.Errorf(
   218  							"persistent Volume %v not deleted by dynamic provisioner: %w", pv.Name, err))
   219  					}
   220  				}
   221  			}
   222  		default:
   223  			framework.Failf("Found PVC (%v) or PV (%v) but not running Preprovisioned or Dynamic test pattern", r.Pvc, r.Pv)
   224  		}
   225  	}
   226  
   227  	if r.Sc != nil {
   228  		ginkgo.By("Deleting sc")
   229  		if err := storageutils.DeleteStorageClass(ctx, f.ClientSet, r.Sc.Name); err != nil {
   230  			cleanUpErrs = append(cleanUpErrs, fmt.Errorf("failed to delete StorageClass %v: %w", r.Sc.Name, err))
   231  		}
   232  	}
   233  
   234  	// Cleanup volume for pre-provisioned volume tests
   235  	if r.Volume != nil {
   236  		if err := storageutils.TryFunc(func() {
   237  			r.Volume.DeleteVolume(ctx)
   238  		}); err != nil {
   239  			cleanUpErrs = append(cleanUpErrs, fmt.Errorf("failed to delete Volume: %w", err))
   240  		}
   241  	}
   242  	return utilerrors.NewAggregate(cleanUpErrs)
   243  }
   244  
   245  func createPVCPV(
   246  	ctx context.Context,
   247  	f *framework.Framework,
   248  	name string,
   249  	pvSource *v1.PersistentVolumeSource,
   250  	volumeNodeAffinity *v1.VolumeNodeAffinity,
   251  	volMode v1.PersistentVolumeMode,
   252  	accessModes []v1.PersistentVolumeAccessMode,
   253  ) (*v1.PersistentVolume, *v1.PersistentVolumeClaim) {
   254  	pvConfig := e2epv.PersistentVolumeConfig{
   255  		NamePrefix:       fmt.Sprintf("%s-", name),
   256  		StorageClassName: f.Namespace.Name,
   257  		PVSource:         *pvSource,
   258  		NodeAffinity:     volumeNodeAffinity,
   259  		AccessModes:      accessModes,
   260  	}
   261  
   262  	pvcConfig := e2epv.PersistentVolumeClaimConfig{
   263  		StorageClassName: &f.Namespace.Name,
   264  		AccessModes:      accessModes,
   265  	}
   266  
   267  	if volMode != "" {
   268  		pvConfig.VolumeMode = &volMode
   269  		pvcConfig.VolumeMode = &volMode
   270  	}
   271  
   272  	framework.Logf("Creating PVC and PV")
   273  	pv, pvc, err := e2epv.CreatePVCPV(ctx, f.ClientSet, f.Timeouts, pvConfig, pvcConfig, f.Namespace.Name, false)
   274  	framework.ExpectNoError(err, "PVC, PV creation failed")
   275  
   276  	err = e2epv.WaitOnPVandPVC(ctx, f.ClientSet, f.Timeouts, f.Namespace.Name, pv, pvc)
   277  	framework.ExpectNoError(err, "PVC, PV failed to bind")
   278  
   279  	return pv, pvc
   280  }
   281  
   282  func createPVCPVFromDynamicProvisionSC(
   283  	ctx context.Context,
   284  	f *framework.Framework,
   285  	name string,
   286  	claimSize string,
   287  	sc *storagev1.StorageClass,
   288  	volMode v1.PersistentVolumeMode,
   289  	accessModes []v1.PersistentVolumeAccessMode,
   290  ) (*v1.PersistentVolume, *v1.PersistentVolumeClaim) {
   291  	cs := f.ClientSet
   292  	ns := f.Namespace.Name
   293  
   294  	ginkgo.By("creating a claim")
   295  	pvcCfg := e2epv.PersistentVolumeClaimConfig{
   296  		NamePrefix:       name,
   297  		ClaimSize:        claimSize,
   298  		StorageClassName: &(sc.Name),
   299  		AccessModes:      accessModes,
   300  		VolumeMode:       &volMode,
   301  	}
   302  
   303  	pvc := e2epv.MakePersistentVolumeClaim(pvcCfg, ns)
   304  
   305  	var err error
   306  	pvc, err = e2epv.CreatePVC(ctx, cs, ns, pvc)
   307  	framework.ExpectNoError(err)
   308  
   309  	if !isDelayedBinding(sc) {
   310  		err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, cs, pvc.Namespace, pvc.Name, framework.Poll, f.Timeouts.ClaimProvision)
   311  		framework.ExpectNoError(err)
   312  	}
   313  
   314  	pvc, err = cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvc.Name, metav1.GetOptions{})
   315  	framework.ExpectNoError(err)
   316  
   317  	var pv *v1.PersistentVolume
   318  	if !isDelayedBinding(sc) {
   319  		pv, err = cs.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
   320  		framework.ExpectNoError(err)
   321  	}
   322  
   323  	return pv, pvc
   324  }
   325  
   326  func isDelayedBinding(sc *storagev1.StorageClass) bool {
   327  	if sc.VolumeBindingMode != nil {
   328  		return *sc.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer
   329  	}
   330  	return false
   331  }