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 }