github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/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 }