k8s.io/kubernetes@v1.29.3/pkg/volume/vsphere_volume/vsphere_volume_util.go (about) 1 //go:build !providerless 2 // +build !providerless 3 4 /* 5 Copyright 2016 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package vsphere_volume 21 22 import ( 23 "errors" 24 "fmt" 25 "strconv" 26 "strings" 27 "time" 28 29 v1 "k8s.io/api/core/v1" 30 cloudprovider "k8s.io/cloud-provider" 31 volumehelpers "k8s.io/cloud-provider/volume/helpers" 32 "k8s.io/klog/v2" 33 "k8s.io/kubernetes/pkg/volume" 34 volumeutil "k8s.io/kubernetes/pkg/volume/util" 35 "k8s.io/legacy-cloud-providers/vsphere" 36 "k8s.io/legacy-cloud-providers/vsphere/vclib" 37 ) 38 39 const ( 40 checkSleepDuration = time.Second 41 diskByIDPath = "/dev/disk/by-id/" 42 diskSCSIPrefix = "wwn-0x" 43 // diskformat parameter is deprecated as of Kubernetes v1.21.0 44 diskformat = "diskformat" 45 datastore = "datastore" 46 StoragePolicyName = "storagepolicyname" 47 48 // hostfailurestotolerate parameter is deprecated as of Kubernetes v1.19.0 49 HostFailuresToTolerateCapability = "hostfailurestotolerate" 50 // forceprovisioning parameter is deprecated as of Kubernetes v1.19.0 51 ForceProvisioningCapability = "forceprovisioning" 52 // cachereservation parameter is deprecated as of Kubernetes v1.19.0 53 CacheReservationCapability = "cachereservation" 54 // diskstripes parameter is deprecated as of Kubernetes v1.19.0 55 DiskStripesCapability = "diskstripes" 56 // objectspacereservation parameter is deprecated as of Kubernetes v1.19.0 57 ObjectSpaceReservationCapability = "objectspacereservation" 58 // iopslimit parameter is deprecated as of Kubernetes v1.19.0 59 IopsLimitCapability = "iopslimit" 60 HostFailuresToTolerateCapabilityMin = 0 61 HostFailuresToTolerateCapabilityMax = 3 62 ForceProvisioningCapabilityMin = 0 63 ForceProvisioningCapabilityMax = 1 64 CacheReservationCapabilityMin = 0 65 CacheReservationCapabilityMax = 100 66 DiskStripesCapabilityMin = 1 67 DiskStripesCapabilityMax = 12 68 ObjectSpaceReservationCapabilityMin = 0 69 ObjectSpaceReservationCapabilityMax = 100 70 IopsLimitCapabilityMin = 0 71 // reduce number of characters in vsphere volume name. The reason for setting length smaller than 255 is because typically 72 // volume name also becomes part of mount path - /var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/<name> 73 // and systemd has a limit of 256 chars in a unit name - https://github.com/systemd/systemd/pull/14294 74 // so if we subtract the kubelet path prefix from 256, we are left with 191 characters. 75 // Since datastore name is typically part of volumeName we are choosing a shorter length of 63 76 // and leaving room of certain characters being escaped etc. 77 // Given that volume name is typically of the form - pvc-0f13e3ad-97f8-41ab-9392-84562ef40d17.vmdk (45 chars), 78 // this should still leave plenty of room for clusterName inclusion. 79 maxVolumeLength = 63 80 ) 81 82 var ErrProbeVolume = errors.New("error scanning attached volumes") 83 84 type VsphereDiskUtil struct{} 85 86 type VolumeSpec struct { 87 Path string 88 Size int 89 Fstype string 90 StoragePolicyID string 91 StoragePolicyName string 92 Labels map[string]string 93 } 94 95 // CreateVolume creates a vSphere volume. 96 func (util *VsphereDiskUtil) CreateVolume(v *vsphereVolumeProvisioner, selectedNode *v1.Node, selectedZone []string) (volSpec *VolumeSpec, err error) { 97 var fstype string 98 cloud, err := getCloudProvider(v.plugin.host.GetCloudProvider()) 99 if err != nil { 100 return nil, err 101 } 102 103 capacity := v.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] 104 // vSphere works with KiB, but its minimum allocation unit is 1 MiB 105 volSizeMiB, err := volumehelpers.RoundUpToMiBInt(capacity) 106 if err != nil { 107 return nil, err 108 } 109 volSizeKiB := volSizeMiB * 1024 110 name := volumeutil.GenerateVolumeName(v.options.ClusterName, v.options.PVName, maxVolumeLength) 111 volumeOptions := &vclib.VolumeOptions{ 112 CapacityKB: volSizeKiB, 113 Tags: *v.options.CloudTags, 114 Name: name, 115 } 116 117 volumeOptions.Zone = selectedZone 118 volumeOptions.SelectedNode = selectedNode 119 // Apply Parameters (case-insensitive). We leave validation of 120 // the values to the cloud provider. 121 for parameter, value := range v.options.Parameters { 122 switch strings.ToLower(parameter) { 123 case diskformat: 124 volumeOptions.DiskFormat = value 125 case datastore: 126 volumeOptions.Datastore = value 127 case volume.VolumeParameterFSType: 128 fstype = value 129 klog.V(4).Infof("Setting fstype as %q", fstype) 130 case StoragePolicyName: 131 volumeOptions.StoragePolicyName = value 132 klog.V(4).Infof("Setting StoragePolicyName as %q", volumeOptions.StoragePolicyName) 133 case HostFailuresToTolerateCapability, ForceProvisioningCapability, 134 CacheReservationCapability, DiskStripesCapability, 135 ObjectSpaceReservationCapability, IopsLimitCapability: 136 capabilityData, err := validateVSANCapability(strings.ToLower(parameter), value) 137 if err != nil { 138 return nil, err 139 } 140 volumeOptions.VSANStorageProfileData += capabilityData 141 default: 142 return nil, fmt.Errorf("invalid option %q for volume plugin %s", parameter, v.plugin.GetPluginName()) 143 } 144 } 145 146 if volumeOptions.VSANStorageProfileData != "" { 147 if volumeOptions.StoragePolicyName != "" { 148 return nil, fmt.Errorf("cannot specify storage policy capabilities along with storage policy name. Please specify only one") 149 } 150 volumeOptions.VSANStorageProfileData = "(" + volumeOptions.VSANStorageProfileData + ")" 151 } 152 klog.V(4).Infof("VSANStorageProfileData in vsphere volume %q", volumeOptions.VSANStorageProfileData) 153 // TODO: implement PVC.Selector parsing 154 if v.options.PVC.Spec.Selector != nil { 155 return nil, fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on vSphere") 156 } 157 158 vmDiskPath, err := cloud.CreateVolume(volumeOptions) 159 if err != nil { 160 return nil, err 161 } 162 labels, err := cloud.GetVolumeLabels(vmDiskPath) 163 if err != nil { 164 return nil, err 165 } 166 volSpec = &VolumeSpec{ 167 Path: vmDiskPath, 168 Size: volSizeKiB, 169 Fstype: fstype, 170 StoragePolicyName: volumeOptions.StoragePolicyName, 171 StoragePolicyID: volumeOptions.StoragePolicyID, 172 Labels: labels, 173 } 174 klog.V(2).Infof("Successfully created vsphere volume %s", name) 175 return volSpec, nil 176 } 177 178 // DeleteVolume deletes a vSphere volume. 179 func (util *VsphereDiskUtil) DeleteVolume(vd *vsphereVolumeDeleter) error { 180 cloud, err := getCloudProvider(vd.plugin.host.GetCloudProvider()) 181 if err != nil { 182 return err 183 } 184 185 if err = cloud.DeleteVolume(vd.volPath); err != nil { 186 klog.V(2).Infof("Error deleting vsphere volume %s: %v", vd.volPath, err) 187 return err 188 } 189 klog.V(2).Infof("Successfully deleted vsphere volume %s", vd.volPath) 190 return nil 191 } 192 193 func getVolPathfromVolumeName(deviceMountPath string) string { 194 // Assumption: No file or folder is named starting with '[' in datastore 195 volPath := deviceMountPath[strings.LastIndex(deviceMountPath, "["):] 196 // space between datastore and vmdk name in volumePath is encoded as '\040' when returned by GetMountRefs(). 197 // volumePath eg: "[local] xxx.vmdk" provided to attach/mount 198 // replacing \040 with space to match the actual volumePath 199 return strings.Replace(volPath, "\\040", " ", -1) 200 } 201 202 func getCloudProvider(cloud cloudprovider.Interface) (*vsphere.VSphere, error) { 203 if cloud == nil { 204 klog.Errorf("Cloud provider not initialized properly") 205 return nil, errors.New("cloud provider not initialized properly") 206 } 207 208 vs, ok := cloud.(*vsphere.VSphere) 209 if !ok || vs == nil { 210 return nil, errors.New("invalid cloud provider: expected vSphere") 211 } 212 return vs, nil 213 } 214 215 // Validate the capability requirement for the user specified policy attributes. 216 func validateVSANCapability(capabilityName string, capabilityValue string) (string, error) { 217 var capabilityData string 218 capabilityIntVal, ok := verifyCapabilityValueIsInteger(capabilityValue) 219 if !ok { 220 return "", fmt.Errorf("invalid value for %s. The capabilityValue: %s must be a valid integer value", capabilityName, capabilityValue) 221 } 222 switch strings.ToLower(capabilityName) { 223 case HostFailuresToTolerateCapability: 224 if capabilityIntVal >= HostFailuresToTolerateCapabilityMin && capabilityIntVal <= HostFailuresToTolerateCapabilityMax { 225 capabilityData = " (\"hostFailuresToTolerate\" i" + capabilityValue + ")" 226 } else { 227 return "", fmt.Errorf(`invalid value for hostFailuresToTolerate. 228 The default value is %d, minimum value is %d and maximum value is %d`, 229 1, HostFailuresToTolerateCapabilityMin, HostFailuresToTolerateCapabilityMax) 230 } 231 case ForceProvisioningCapability: 232 if capabilityIntVal >= ForceProvisioningCapabilityMin && capabilityIntVal <= ForceProvisioningCapabilityMax { 233 capabilityData = " (\"forceProvisioning\" i" + capabilityValue + ")" 234 } else { 235 return "", fmt.Errorf(`invalid value for forceProvisioning. 236 The value can be either %d or %d`, 237 ForceProvisioningCapabilityMin, ForceProvisioningCapabilityMax) 238 } 239 case CacheReservationCapability: 240 if capabilityIntVal >= CacheReservationCapabilityMin && capabilityIntVal <= CacheReservationCapabilityMax { 241 capabilityData = " (\"cacheReservation\" i" + strconv.Itoa(capabilityIntVal*10000) + ")" 242 } else { 243 return "", fmt.Errorf(`invalid value for cacheReservation. 244 The minimum percentage is %d and maximum percentage is %d`, 245 CacheReservationCapabilityMin, CacheReservationCapabilityMax) 246 } 247 case DiskStripesCapability: 248 if capabilityIntVal >= DiskStripesCapabilityMin && capabilityIntVal <= DiskStripesCapabilityMax { 249 capabilityData = " (\"stripeWidth\" i" + capabilityValue + ")" 250 } else { 251 return "", fmt.Errorf(`invalid value for diskStripes. 252 The minimum value is %d and maximum value is %d`, 253 DiskStripesCapabilityMin, DiskStripesCapabilityMax) 254 } 255 case ObjectSpaceReservationCapability: 256 if capabilityIntVal >= ObjectSpaceReservationCapabilityMin && capabilityIntVal <= ObjectSpaceReservationCapabilityMax { 257 capabilityData = " (\"proportionalCapacity\" i" + capabilityValue + ")" 258 } else { 259 return "", fmt.Errorf(`invalid value for ObjectSpaceReservation. 260 The minimum percentage is %d and maximum percentage is %d`, 261 ObjectSpaceReservationCapabilityMin, ObjectSpaceReservationCapabilityMax) 262 } 263 case IopsLimitCapability: 264 if capabilityIntVal >= IopsLimitCapabilityMin { 265 capabilityData = " (\"iopsLimit\" i" + capabilityValue + ")" 266 } else { 267 return "", fmt.Errorf(`invalid value for iopsLimit. 268 The value should be greater than %d`, IopsLimitCapabilityMin) 269 } 270 } 271 return capabilityData, nil 272 } 273 274 // Verify if the capability value is of type integer. 275 func verifyCapabilityValueIsInteger(capabilityValue string) (int, bool) { 276 i, err := strconv.Atoi(capabilityValue) 277 if err != nil { 278 return -1, false 279 } 280 return i, true 281 }