github.com/Equinix-Metal/virtlet@v1.5.2-0.20210807010419-342346535dc5/tests/e2e/framework/pvc_interface.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     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  	"errors"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"k8s.io/api/core/v1"
    26  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/api/resource"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  )
    30  
    31  // PVCSpec describes a PVC+PV pair to create.
    32  type PVCSpec struct {
    33  	// The name of PVC. The PV name is derived by adding the "-pv"
    34  	// suffix.
    35  	Name string
    36  	// The size of PV. Must be parseable as k8s resource quantity
    37  	// (e.g. 10M).
    38  	Size string
    39  	// If non-empty, specifies the node name for local PV.
    40  	NodeName string
    41  	// If true, specifies the block volume mode, otherwise
    42  	// filesystem volume mode is used.
    43  	Block bool
    44  	// In block volume mode, the path to the block device inside the VM,
    45  	// empty value means not referencing the volume in volumeDevices.
    46  	// In filesystem volume mode, the path inside the VM for mounting the volume,
    47  	// empty value means not mounting the volume.
    48  	ContainerPath string
    49  	// For local PVs, specifies the path to the directory when
    50  	// using filesystem mode, and the path to the block device in
    51  	// the block mode.
    52  	LocalPath string
    53  	// Ceph RBD image name.
    54  	CephRBDImage string
    55  	// Ceph monitor IP.
    56  	CephMonitorIP string
    57  	// Ceph pool name for the RBD.
    58  	CephRBDPool string
    59  	// The name of Kubernetes secret to use for Ceph.
    60  	CephSecretName string
    61  	// FlexVolume options for Virtlet flexvolume driver.
    62  	FlexVolumeOptions map[string]string
    63  }
    64  
    65  func (spec PVCSpec) pvSource(namespace string) v1.PersistentVolumeSource {
    66  	switch {
    67  	case spec.LocalPath != "":
    68  		if spec.CephRBDImage != "" || len(spec.FlexVolumeOptions) > 0 {
    69  			panic("Can only use one of LocalPath, CephRBDImage and FlexVolumeOptions at the same time")
    70  		}
    71  		return v1.PersistentVolumeSource{
    72  			Local: &v1.LocalVolumeSource{
    73  				Path: spec.LocalPath,
    74  			},
    75  		}
    76  	case spec.CephRBDImage != "" && len(spec.FlexVolumeOptions) > 0:
    77  		panic("Can only use one of LocalPath, CephRBDImage and FlexVolumeOptions at the same time")
    78  	case spec.CephRBDImage != "":
    79  		return v1.PersistentVolumeSource{
    80  			RBD: &v1.RBDPersistentVolumeSource{
    81  				CephMonitors: []string{spec.CephMonitorIP},
    82  				RBDImage:     spec.CephRBDImage,
    83  				RBDPool:      spec.CephRBDPool,
    84  				SecretRef: &v1.SecretReference{
    85  					Name:      spec.CephSecretName,
    86  					Namespace: namespace,
    87  				},
    88  			},
    89  		}
    90  	case len(spec.FlexVolumeOptions) > 0:
    91  		return v1.PersistentVolumeSource{
    92  			FlexVolume: &v1.FlexPersistentVolumeSource{
    93  				Driver:  "virtlet/flexvolume_driver",
    94  				Options: spec.FlexVolumeOptions,
    95  			},
    96  		}
    97  	default:
    98  		panic("bad PV/PVC spec")
    99  	}
   100  }
   101  
   102  func (spec PVCSpec) nodeAffinity() *v1.VolumeNodeAffinity {
   103  	if spec.NodeName == "" {
   104  		return nil
   105  	}
   106  	return &v1.VolumeNodeAffinity{
   107  		Required: &v1.NodeSelector{
   108  			NodeSelectorTerms: []v1.NodeSelectorTerm{
   109  				{
   110  					MatchExpressions: []v1.NodeSelectorRequirement{
   111  						{
   112  							Key:      "kubernetes.io/hostname",
   113  							Operator: "In",
   114  							Values:   []string{spec.NodeName},
   115  						},
   116  					},
   117  				},
   118  			},
   119  		},
   120  	}
   121  }
   122  
   123  // PVCInterface is used to work with PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs).
   124  type PVCInterface struct {
   125  	controller *Controller
   126  	// Spec for the PV and PVC.
   127  	Spec PVCSpec
   128  	// Kubernetes PV object
   129  	Volume *v1.PersistentVolume
   130  	// Kubernetes PVC object
   131  	Claim *v1.PersistentVolumeClaim
   132  }
   133  
   134  func newPersistentVolumeClaim(controller *Controller, spec PVCSpec) *PVCInterface {
   135  	volMode := v1.PersistentVolumeFilesystem
   136  	if spec.Block {
   137  		volMode = v1.PersistentVolumeBlock
   138  	}
   139  	return &PVCInterface{
   140  		controller: controller,
   141  		Spec:       spec,
   142  		Volume: &v1.PersistentVolume{
   143  			ObjectMeta: metav1.ObjectMeta{
   144  				Name: spec.Name + "-pv",
   145  			},
   146  			Spec: v1.PersistentVolumeSpec{
   147  				Capacity: v1.ResourceList{
   148  					v1.ResourceStorage: resource.MustParse(spec.Size),
   149  				},
   150  				VolumeMode:  &volMode,
   151  				AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   152  				ClaimRef: &v1.ObjectReference{
   153  					Name:      spec.Name,
   154  					Namespace: controller.Namespace(),
   155  				},
   156  				PersistentVolumeSource: spec.pvSource(controller.Namespace()),
   157  				NodeAffinity:           spec.nodeAffinity(),
   158  			},
   159  		},
   160  		Claim: &v1.PersistentVolumeClaim{
   161  			ObjectMeta: metav1.ObjectMeta{
   162  				Name: spec.Name,
   163  			},
   164  			Spec: v1.PersistentVolumeClaimSpec{
   165  				AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   166  				VolumeMode:  &volMode,
   167  				Resources: v1.ResourceRequirements{
   168  					Requests: v1.ResourceList{
   169  						v1.ResourceStorage: resource.MustParse(spec.Size),
   170  					},
   171  				},
   172  			},
   173  		},
   174  	}
   175  }
   176  
   177  // AddToPod adds the volume to the pod referencing the PVC.
   178  func (pvci *PVCInterface) AddToPod(pi *PodInterface, name string) {
   179  	pi.Pod.Spec.Volumes = append(pi.Pod.Spec.Volumes, v1.Volume{
   180  		Name: name,
   181  		VolumeSource: v1.VolumeSource{
   182  			PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   183  				ClaimName: pvci.Claim.Name,
   184  			},
   185  		},
   186  	})
   187  	if pvci.Spec.ContainerPath != "" {
   188  		c := &pi.Pod.Spec.Containers[0]
   189  		if pvci.Spec.Block {
   190  			c.VolumeDevices = append(c.VolumeDevices, v1.VolumeDevice{
   191  				Name:       name,
   192  				DevicePath: pvci.Spec.ContainerPath,
   193  			})
   194  		} else {
   195  			c.VolumeMounts = append(c.VolumeMounts, v1.VolumeMount{
   196  				Name:      name,
   197  				MountPath: pvci.Spec.ContainerPath,
   198  			})
   199  		}
   200  	}
   201  }
   202  
   203  // Create creates the PVC and its corresponding PV.
   204  func (pvci *PVCInterface) Create() error {
   205  	updatedPV, err := pvci.controller.PersistentVolumesClient().Create(pvci.Volume)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	pvci.Volume = updatedPV
   210  	updatedPVC, err := pvci.controller.PersistentVolumeClaimsClient().Create(pvci.Claim)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	pvci.Claim = updatedPVC
   215  	return nil
   216  }
   217  
   218  // Delete deletes the PVC and its corresponding PV.
   219  // It doesn't return an error if either PVC or PV doesn't exist.
   220  func (pvci *PVCInterface) Delete() error {
   221  	var errs []string
   222  	if err := pvci.controller.PersistentVolumeClaimsClient().Delete(pvci.Claim.Name, nil); err != nil && !k8serrors.IsNotFound(err) {
   223  		errs = append(errs, fmt.Sprintf("error deleting pvc %q: %v", pvci.Claim.Name, err))
   224  	}
   225  	if err := pvci.controller.PersistentVolumesClient().Delete(pvci.Volume.Name, nil); err != nil && !k8serrors.IsNotFound(err) {
   226  		errs = append(errs, fmt.Sprintf("error deleting pv %q: %v", pvci.Volume.Name, err))
   227  	}
   228  	if len(errs) == 0 {
   229  		return nil
   230  	}
   231  	return errors.New(strings.Join(errs, "\n"))
   232  }
   233  
   234  // WaitForDestruction waits for the PV and PVC to be deleted.
   235  func (pvci *PVCInterface) WaitForDestruction(timing ...time.Duration) error {
   236  	timeout := time.Minute * 5
   237  	pollPeriond := time.Second
   238  	consistencyPeriod := time.Second * 5
   239  	if len(timing) > 0 {
   240  		timeout = timing[0]
   241  	}
   242  	if len(timing) > 1 {
   243  		pollPeriond = timing[1]
   244  	}
   245  	if len(timing) > 2 {
   246  		consistencyPeriod = timing[2]
   247  	}
   248  
   249  	return waitForConsistentState(func() error {
   250  		switch _, err := pvci.controller.PersistentVolumeClaimsClient().Get(pvci.Claim.Name, metav1.GetOptions{}); {
   251  		case err == nil:
   252  			return errors.New("PVC not deleted")
   253  		case !k8serrors.IsNotFound(err):
   254  			return err
   255  		}
   256  		switch _, err := pvci.controller.PersistentVolumesClient().Get(pvci.Volume.Name, metav1.GetOptions{}); {
   257  		case err == nil:
   258  			return errors.New("PV not deleted")
   259  		case !k8serrors.IsNotFound(err):
   260  			return err
   261  		}
   262  		return nil
   263  	}, timeout, pollPeriond, consistencyPeriod)
   264  }