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  }