k8s.io/kubernetes@v1.29.3/pkg/volume/vsphere_volume/vsphere_volume.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  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"runtime"
    27  	"strings"
    28  
    29  	"k8s.io/klog/v2"
    30  	"k8s.io/mount-utils"
    31  	utilstrings "k8s.io/utils/strings"
    32  
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/api/resource"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	volumehelpers "k8s.io/cloud-provider/volume/helpers"
    38  
    39  	"k8s.io/kubernetes/pkg/volume"
    40  	"k8s.io/kubernetes/pkg/volume/util"
    41  )
    42  
    43  // This is the primary entrypoint for volume plugins.
    44  func ProbeVolumePlugins() []volume.VolumePlugin {
    45  	return []volume.VolumePlugin{&vsphereVolumePlugin{}}
    46  }
    47  
    48  type vsphereVolumePlugin struct {
    49  	host volume.VolumeHost
    50  }
    51  
    52  var _ volume.VolumePlugin = &vsphereVolumePlugin{}
    53  var _ volume.PersistentVolumePlugin = &vsphereVolumePlugin{}
    54  var _ volume.DeletableVolumePlugin = &vsphereVolumePlugin{}
    55  var _ volume.ProvisionableVolumePlugin = &vsphereVolumePlugin{}
    56  
    57  const (
    58  	vsphereVolumePluginName = "kubernetes.io/vsphere-volume"
    59  )
    60  
    61  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    62  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(vsphereVolumePluginName), volName)
    63  }
    64  
    65  // vSphere Volume Plugin
    66  func (plugin *vsphereVolumePlugin) Init(host volume.VolumeHost) error {
    67  	plugin.host = host
    68  	return nil
    69  }
    70  
    71  func (plugin *vsphereVolumePlugin) GetPluginName() string {
    72  	return vsphereVolumePluginName
    73  }
    74  
    75  func (plugin *vsphereVolumePlugin) IsMigratedToCSI() bool {
    76  	return true
    77  }
    78  
    79  func (plugin *vsphereVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    80  	volumeSource, _, err := getVolumeSource(spec)
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  
    85  	return volumeSource.VolumePath, nil
    86  }
    87  
    88  func (plugin *vsphereVolumePlugin) CanSupport(spec *volume.Spec) bool {
    89  	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.VsphereVolume != nil) ||
    90  		(spec.Volume != nil && spec.Volume.VsphereVolume != nil)
    91  }
    92  
    93  func (plugin *vsphereVolumePlugin) RequiresRemount(spec *volume.Spec) bool {
    94  	return false
    95  }
    96  
    97  func (plugin *vsphereVolumePlugin) SupportsMountOption() bool {
    98  	return true
    99  }
   100  
   101  func (plugin *vsphereVolumePlugin) SupportsBulkVolumeVerification() bool {
   102  	return true
   103  }
   104  
   105  func (plugin *vsphereVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
   106  	return false, nil
   107  }
   108  
   109  func (plugin *vsphereVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
   110  	return plugin.newMounterInternal(spec, pod.UID, &VsphereDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
   111  }
   112  
   113  func (plugin *vsphereVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   114  	return plugin.newUnmounterInternal(volName, podUID, &VsphereDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
   115  }
   116  
   117  func (plugin *vsphereVolumePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager vdManager, mounter mount.Interface) (volume.Mounter, error) {
   118  	vvol, _, err := getVolumeSource(spec)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	volPath := vvol.VolumePath
   124  	fsType := vvol.FSType
   125  
   126  	return &vsphereVolumeMounter{
   127  		vsphereVolume: &vsphereVolume{
   128  			podUID:          podUID,
   129  			volName:         spec.Name(),
   130  			volPath:         volPath,
   131  			manager:         manager,
   132  			mounter:         mounter,
   133  			plugin:          plugin,
   134  			MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)),
   135  		},
   136  		fsType:       fsType,
   137  		diskMounter:  util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
   138  		mountOptions: util.MountOptionFromSpec(spec),
   139  	}, nil
   140  }
   141  
   142  func (plugin *vsphereVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, manager vdManager, mounter mount.Interface) (volume.Unmounter, error) {
   143  	return &vsphereVolumeUnmounter{
   144  		&vsphereVolume{
   145  			podUID:          podUID,
   146  			volName:         volName,
   147  			manager:         manager,
   148  			mounter:         mounter,
   149  			plugin:          plugin,
   150  			MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
   151  		}}, nil
   152  }
   153  
   154  func (plugin *vsphereVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   155  	mounter := plugin.host.GetMounter(plugin.GetPluginName())
   156  	kvh, ok := plugin.host.(volume.KubeletVolumeHost)
   157  	if !ok {
   158  		return volume.ReconstructedVolume{}, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
   159  	}
   160  	hu := kvh.GetHostUtil()
   161  	pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName())
   162  	volumePath, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir)
   163  	if err != nil {
   164  		return volume.ReconstructedVolume{}, err
   165  	}
   166  	volumePath = strings.Replace(volumePath, "\\040", " ", -1)
   167  	klog.V(5).Infof("vSphere volume path is %q", volumePath)
   168  	vsphereVolume := &v1.Volume{
   169  		Name: volumeName,
   170  		VolumeSource: v1.VolumeSource{
   171  			VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
   172  				VolumePath: volumePath,
   173  			},
   174  		},
   175  	}
   176  	return volume.ReconstructedVolume{
   177  		Spec: volume.NewSpecFromVolume(vsphereVolume),
   178  	}, nil
   179  }
   180  
   181  // Abstract interface to disk operations.
   182  type vdManager interface {
   183  	// Creates a volume
   184  	CreateVolume(provisioner *vsphereVolumeProvisioner, selectedNode *v1.Node, selectedZone []string) (volSpec *VolumeSpec, err error)
   185  	// Deletes a volume
   186  	DeleteVolume(deleter *vsphereVolumeDeleter) error
   187  }
   188  
   189  // vspherePersistentDisk volumes are disk resources are attached to the kubelet's host machine and exposed to the pod.
   190  type vsphereVolume struct {
   191  	volName string
   192  	podUID  types.UID
   193  	// Unique identifier of the volume, used to find the disk resource in the provider.
   194  	volPath string
   195  	// Utility interface that provides API calls to the provider to attach/detach disks.
   196  	manager vdManager
   197  	// Mounter interface that provides system calls to mount the global path to the pod local path.
   198  	mounter mount.Interface
   199  	plugin  *vsphereVolumePlugin
   200  	volume.MetricsProvider
   201  }
   202  
   203  var _ volume.Mounter = &vsphereVolumeMounter{}
   204  
   205  type vsphereVolumeMounter struct {
   206  	*vsphereVolume
   207  	fsType       string
   208  	diskMounter  *mount.SafeFormatAndMount
   209  	mountOptions []string
   210  }
   211  
   212  func (b *vsphereVolumeMounter) GetAttributes() volume.Attributes {
   213  	return volume.Attributes{
   214  		SELinuxRelabel: true,
   215  		Managed:        true,
   216  	}
   217  }
   218  
   219  // SetUp attaches the disk and bind mounts to the volume path.
   220  func (b *vsphereVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   221  	return b.SetUpAt(b.GetPath(), mounterArgs)
   222  }
   223  
   224  // SetUp attaches the disk and bind mounts to the volume path.
   225  func (b *vsphereVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   226  	klog.V(5).Infof("vSphere volume setup %s to %s", b.volPath, dir)
   227  
   228  	// TODO: handle failed mounts here.
   229  	notmnt, err := b.mounter.IsLikelyNotMountPoint(dir)
   230  	if err != nil && !os.IsNotExist(err) {
   231  		klog.V(4).Infof("IsLikelyNotMountPoint failed: %v", err)
   232  		return err
   233  	}
   234  	if !notmnt {
   235  		klog.V(4).Infof("Something is already mounted to target %s", dir)
   236  		return nil
   237  	}
   238  
   239  	if runtime.GOOS != "windows" {
   240  		// On Windows, Mount will create the parent of dir and mklink (create a symbolic link) at dir later, so don't create a
   241  		// directory at dir now. Otherwise mklink will error: "Cannot create a file when that file already exists".
   242  		if err := os.MkdirAll(dir, 0750); err != nil {
   243  			klog.Errorf("Could not create directory %s: %v", dir, err)
   244  			return err
   245  		}
   246  	}
   247  
   248  	options := []string{"bind"}
   249  
   250  	// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
   251  	globalPDPath := makeGlobalPDPath(b.plugin.host, b.volPath)
   252  	mountOptions := util.JoinMountOptions(options, b.mountOptions)
   253  	err = b.mounter.MountSensitiveWithoutSystemd(globalPDPath, dir, "", mountOptions, nil)
   254  	if err != nil {
   255  		notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
   256  		if mntErr != nil {
   257  			klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
   258  			return err
   259  		}
   260  		if !notmnt {
   261  			if mntErr = b.mounter.Unmount(dir); mntErr != nil {
   262  				klog.Errorf("Failed to unmount: %v", mntErr)
   263  				return err
   264  			}
   265  			notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
   266  			if mntErr != nil {
   267  				klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
   268  				return err
   269  			}
   270  			if !notmnt {
   271  				klog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", b.GetPath())
   272  				return err
   273  			}
   274  		}
   275  		os.Remove(dir)
   276  		return err
   277  	}
   278  	volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil))
   279  	klog.V(3).Infof("vSphere volume %s mounted to %s", b.volPath, dir)
   280  
   281  	return nil
   282  }
   283  
   284  var _ volume.Unmounter = &vsphereVolumeUnmounter{}
   285  
   286  type vsphereVolumeUnmounter struct {
   287  	*vsphereVolume
   288  }
   289  
   290  // Unmounts the bind mount, and detaches the disk only if the PD
   291  // resource was the last reference to that disk on the kubelet.
   292  func (v *vsphereVolumeUnmounter) TearDown() error {
   293  	return v.TearDownAt(v.GetPath())
   294  }
   295  
   296  // Unmounts the bind mount, and detaches the disk only if the PD
   297  // resource was the last reference to that disk on the kubelet.
   298  func (v *vsphereVolumeUnmounter) TearDownAt(dir string) error {
   299  	return mount.CleanupMountPoint(dir, v.mounter, false)
   300  }
   301  
   302  func makeGlobalPDPath(host volume.VolumeHost, devName string) string {
   303  	return filepath.Join(host.GetPluginDir(vsphereVolumePluginName), util.MountsInGlobalPDPath, devName)
   304  }
   305  
   306  func (vv *vsphereVolume) GetPath() string {
   307  	name := vsphereVolumePluginName
   308  	return vv.plugin.host.GetPodVolumeDir(vv.podUID, utilstrings.EscapeQualifiedName(name), vv.volName)
   309  }
   310  
   311  // vSphere Persistent Volume Plugin
   312  func (plugin *vsphereVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
   313  	return []v1.PersistentVolumeAccessMode{
   314  		v1.ReadWriteOnce,
   315  	}
   316  }
   317  
   318  // vSphere Deletable Volume Plugin
   319  type vsphereVolumeDeleter struct {
   320  	*vsphereVolume
   321  }
   322  
   323  var _ volume.Deleter = &vsphereVolumeDeleter{}
   324  
   325  func (plugin *vsphereVolumePlugin) NewDeleter(logger klog.Logger, spec *volume.Spec) (volume.Deleter, error) {
   326  	return plugin.newDeleterInternal(spec, &VsphereDiskUtil{})
   327  }
   328  
   329  func (plugin *vsphereVolumePlugin) newDeleterInternal(spec *volume.Spec, manager vdManager) (volume.Deleter, error) {
   330  	if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.VsphereVolume == nil {
   331  		return nil, fmt.Errorf("spec.PersistentVolumeSource.VsphereVolume is nil")
   332  	}
   333  	return &vsphereVolumeDeleter{
   334  		&vsphereVolume{
   335  			volName: spec.Name(),
   336  			volPath: spec.PersistentVolume.Spec.VsphereVolume.VolumePath,
   337  			manager: manager,
   338  			plugin:  plugin,
   339  		}}, nil
   340  }
   341  
   342  func (r *vsphereVolumeDeleter) Delete() error {
   343  	return r.manager.DeleteVolume(r)
   344  }
   345  
   346  // vSphere Provisionable Volume Plugin
   347  type vsphereVolumeProvisioner struct {
   348  	*vsphereVolume
   349  	options volume.VolumeOptions
   350  }
   351  
   352  var _ volume.Provisioner = &vsphereVolumeProvisioner{}
   353  
   354  func (plugin *vsphereVolumePlugin) NewProvisioner(logger klog.Logger, options volume.VolumeOptions) (volume.Provisioner, error) {
   355  	return plugin.newProvisionerInternal(options, &VsphereDiskUtil{})
   356  }
   357  
   358  func (plugin *vsphereVolumePlugin) newProvisionerInternal(options volume.VolumeOptions, manager vdManager) (volume.Provisioner, error) {
   359  	return &vsphereVolumeProvisioner{
   360  		vsphereVolume: &vsphereVolume{
   361  			manager: manager,
   362  			plugin:  plugin,
   363  		},
   364  		options: options,
   365  	}, nil
   366  }
   367  
   368  func (v *vsphereVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
   369  	if !util.ContainsAllAccessModes(v.plugin.GetAccessModes(), v.options.PVC.Spec.AccessModes) {
   370  		return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes())
   371  	}
   372  	klog.V(1).Infof("Provision with selectedNode: %s and allowedTopologies : %s", getNodeName(selectedNode), allowedTopologies)
   373  	selectedZones, err := volumehelpers.ZonesFromAllowedTopologies(allowedTopologies)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	klog.V(4).Infof("Selected zones for volume : %s", selectedZones)
   379  	volSpec, err := v.manager.CreateVolume(v, selectedNode, selectedZones.List())
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  
   384  	if volSpec.Fstype == "" {
   385  		volSpec.Fstype = "ext4"
   386  	}
   387  
   388  	volumeMode := v.options.PVC.Spec.VolumeMode
   389  	if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock {
   390  		klog.V(5).Infof("vSphere block volume should not have any FSType")
   391  		volSpec.Fstype = ""
   392  	}
   393  
   394  	pv := &v1.PersistentVolume{
   395  		ObjectMeta: metav1.ObjectMeta{
   396  			Name:   v.options.PVName,
   397  			Labels: map[string]string{},
   398  			Annotations: map[string]string{
   399  				util.VolumeDynamicallyCreatedByKey: "vsphere-volume-dynamic-provisioner",
   400  			},
   401  		},
   402  		Spec: v1.PersistentVolumeSpec{
   403  			PersistentVolumeReclaimPolicy: v.options.PersistentVolumeReclaimPolicy,
   404  			AccessModes:                   v.options.PVC.Spec.AccessModes,
   405  			Capacity: v1.ResourceList{
   406  				v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dKi", volSpec.Size)),
   407  			},
   408  			VolumeMode: volumeMode,
   409  			PersistentVolumeSource: v1.PersistentVolumeSource{
   410  				VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
   411  					VolumePath:        volSpec.Path,
   412  					FSType:            volSpec.Fstype,
   413  					StoragePolicyName: volSpec.StoragePolicyName,
   414  					StoragePolicyID:   volSpec.StoragePolicyID,
   415  				},
   416  			},
   417  			MountOptions: v.options.MountOptions,
   418  		},
   419  	}
   420  	if len(v.options.PVC.Spec.AccessModes) == 0 {
   421  		pv.Spec.AccessModes = v.plugin.GetAccessModes()
   422  	}
   423  
   424  	labels := volSpec.Labels
   425  	requirements := make([]v1.NodeSelectorRequirement, 0)
   426  	if len(labels) != 0 {
   427  		if pv.Labels == nil {
   428  			pv.Labels = make(map[string]string)
   429  		}
   430  		for k, v := range labels {
   431  			pv.Labels[k] = v
   432  			var values []string
   433  			if k == v1.LabelTopologyZone || k == v1.LabelFailureDomainBetaZone {
   434  				values, err = volumehelpers.LabelZonesToList(v)
   435  				if err != nil {
   436  					return nil, fmt.Errorf("failed to convert label string for Zone: %s to a List: %v", v, err)
   437  				}
   438  			} else {
   439  				values = []string{v}
   440  			}
   441  			requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: values})
   442  		}
   443  	}
   444  
   445  	if len(requirements) > 0 {
   446  		pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity)
   447  		pv.Spec.NodeAffinity.Required = new(v1.NodeSelector)
   448  		pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1)
   449  		pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = requirements
   450  	}
   451  
   452  	return pv, nil
   453  }
   454  
   455  func getVolumeSource(
   456  	spec *volume.Spec) (*v1.VsphereVirtualDiskVolumeSource, bool, error) {
   457  	if spec.Volume != nil && spec.Volume.VsphereVolume != nil {
   458  		return spec.Volume.VsphereVolume, spec.ReadOnly, nil
   459  	} else if spec.PersistentVolume != nil &&
   460  		spec.PersistentVolume.Spec.VsphereVolume != nil {
   461  		return spec.PersistentVolume.Spec.VsphereVolume, spec.ReadOnly, nil
   462  	}
   463  
   464  	return nil, false, fmt.Errorf("spec does not reference a VSphere volume type")
   465  }
   466  
   467  func getNodeName(node *v1.Node) string {
   468  	if node == nil {
   469  		return ""
   470  	}
   471  	return node.Name
   472  }