k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/local/local.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 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 local 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "runtime" 24 "strings" 25 26 "k8s.io/klog/v2" 27 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/client-go/tools/record" 32 "k8s.io/kubernetes/pkg/kubelet/events" 33 "k8s.io/kubernetes/pkg/volume" 34 "k8s.io/kubernetes/pkg/volume/util" 35 "k8s.io/kubernetes/pkg/volume/util/hostutil" 36 "k8s.io/kubernetes/pkg/volume/validation" 37 "k8s.io/mount-utils" 38 "k8s.io/utils/keymutex" 39 utilstrings "k8s.io/utils/strings" 40 ) 41 42 const ( 43 defaultFSType = "ext4" 44 ) 45 46 // ProbeVolumePlugins is the primary entrypoint for volume plugins. 47 func ProbeVolumePlugins() []volume.VolumePlugin { 48 return []volume.VolumePlugin{&localVolumePlugin{}} 49 } 50 51 type localVolumePlugin struct { 52 host volume.VolumeHost 53 volumeLocks keymutex.KeyMutex 54 recorder record.EventRecorder 55 } 56 57 var _ volume.VolumePlugin = &localVolumePlugin{} 58 var _ volume.PersistentVolumePlugin = &localVolumePlugin{} 59 var _ volume.BlockVolumePlugin = &localVolumePlugin{} 60 var _ volume.NodeExpandableVolumePlugin = &localVolumePlugin{} 61 62 const ( 63 localVolumePluginName = "kubernetes.io/local-volume" 64 ) 65 66 func (plugin *localVolumePlugin) Init(host volume.VolumeHost) error { 67 plugin.host = host 68 plugin.volumeLocks = keymutex.NewHashed(0) 69 plugin.recorder = host.GetEventRecorder() 70 return nil 71 } 72 73 func (plugin *localVolumePlugin) GetPluginName() string { 74 return localVolumePluginName 75 } 76 77 func (plugin *localVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) { 78 // This volume is only supported as a PersistentVolumeSource, so the PV name is unique 79 return spec.Name(), nil 80 } 81 82 func (plugin *localVolumePlugin) CanSupport(spec *volume.Spec) bool { 83 // This volume is only supported as a PersistentVolumeSource 84 return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Local != nil) 85 } 86 87 func (plugin *localVolumePlugin) RequiresRemount(spec *volume.Spec) bool { 88 return false 89 } 90 91 func (plugin *localVolumePlugin) SupportsMountOption() bool { 92 return true 93 } 94 95 func (plugin *localVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 96 return false, nil 97 } 98 99 func (plugin *localVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 100 // The current meaning of AccessMode is how many nodes can attach to it, not how many pods can mount it 101 return []v1.PersistentVolumeAccessMode{ 102 v1.ReadWriteOnce, 103 } 104 } 105 106 func getVolumeSource(spec *volume.Spec) (*v1.LocalVolumeSource, bool, error) { 107 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Local != nil { 108 return spec.PersistentVolume.Spec.Local, spec.ReadOnly, nil 109 } 110 111 return nil, false, fmt.Errorf("Spec does not reference a Local volume type") 112 } 113 114 func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { 115 _, readOnly, err := getVolumeSource(spec) 116 if err != nil { 117 return nil, err 118 } 119 120 globalLocalPath, err := plugin.getGlobalLocalPath(spec) 121 if err != nil { 122 return nil, err 123 } 124 125 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 126 if !ok { 127 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 128 } 129 130 return &localVolumeMounter{ 131 localVolume: &localVolume{ 132 pod: pod, 133 podUID: pod.UID, 134 volName: spec.Name(), 135 mounter: plugin.host.GetMounter(plugin.GetPluginName()), 136 hostUtil: kvh.GetHostUtil(), 137 plugin: plugin, 138 globalPath: globalLocalPath, 139 MetricsProvider: volume.NewMetricsStatFS(plugin.host.GetPodVolumeDir(pod.UID, utilstrings.EscapeQualifiedName(localVolumePluginName), spec.Name())), 140 }, 141 mountOptions: util.MountOptionFromSpec(spec), 142 readOnly: readOnly, 143 }, nil 144 145 } 146 147 func (plugin *localVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 148 return &localVolumeUnmounter{ 149 localVolume: &localVolume{ 150 podUID: podUID, 151 volName: volName, 152 mounter: plugin.host.GetMounter(plugin.GetPluginName()), 153 plugin: plugin, 154 }, 155 }, nil 156 } 157 158 func (plugin *localVolumePlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, 159 _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { 160 volumeSource, readOnly, err := getVolumeSource(spec) 161 if err != nil { 162 return nil, err 163 } 164 165 mapper := &localVolumeMapper{ 166 localVolume: &localVolume{ 167 podUID: pod.UID, 168 volName: spec.Name(), 169 globalPath: volumeSource.Path, 170 plugin: plugin, 171 }, 172 readOnly: readOnly, 173 } 174 175 blockPath, err := mapper.GetGlobalMapPath(spec) 176 if err != nil { 177 return nil, fmt.Errorf("failed to get device path: %v", err) 178 } 179 mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(pod.UID))) 180 181 return mapper, nil 182 } 183 184 func (plugin *localVolumePlugin) NewBlockVolumeUnmapper(volName string, 185 podUID types.UID) (volume.BlockVolumeUnmapper, error) { 186 return &localVolumeUnmapper{ 187 localVolume: &localVolume{ 188 podUID: podUID, 189 volName: volName, 190 plugin: plugin, 191 }, 192 }, nil 193 } 194 195 // TODO: check if no path and no topology constraints are ok 196 func (plugin *localVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 197 fs := v1.PersistentVolumeFilesystem 198 // The main purpose of reconstructed volume is to clean unused mount points 199 // and directories. 200 // For filesystem volume with directory source, no global mount path is 201 // needed to clean. Empty path is ok. 202 // For filesystem volume with block source, we should resolve to its device 203 // path if global mount path exists. 204 var path string 205 mounter := plugin.host.GetMounter(plugin.GetPluginName()) 206 refs, err := mounter.GetMountRefs(mountPath) 207 if err != nil { 208 return volume.ReconstructedVolume{}, err 209 } 210 baseMountPath := plugin.generateBlockDeviceBaseGlobalPath() 211 for _, ref := range refs { 212 if mount.PathWithinBase(ref, baseMountPath) { 213 // If the global mount for block device exists, the source is block 214 // device. 215 // The resolved device path may not be the exact same as path in 216 // local PV object if symbolic link is used. However, it's the true 217 // source and can be used in reconstructed volume. 218 path, _, err = mount.GetDeviceNameFromMount(mounter, ref) 219 if err != nil { 220 return volume.ReconstructedVolume{}, err 221 } 222 klog.V(4).Infof("local: reconstructing volume %q (pod volume mount: %q) with device %q", volumeName, mountPath, path) 223 break 224 } 225 } 226 localVolume := &v1.PersistentVolume{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: volumeName, 229 }, 230 Spec: v1.PersistentVolumeSpec{ 231 PersistentVolumeSource: v1.PersistentVolumeSource{ 232 Local: &v1.LocalVolumeSource{ 233 Path: path, 234 }, 235 }, 236 VolumeMode: &fs, 237 }, 238 } 239 return volume.ReconstructedVolume{ 240 Spec: volume.NewSpecFromPersistentVolume(localVolume, false), 241 }, nil 242 } 243 244 func (plugin *localVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, 245 mapPath string) (*volume.Spec, error) { 246 block := v1.PersistentVolumeBlock 247 248 localVolume := &v1.PersistentVolume{ 249 ObjectMeta: metav1.ObjectMeta{ 250 Name: volumeName, 251 }, 252 Spec: v1.PersistentVolumeSpec{ 253 PersistentVolumeSource: v1.PersistentVolumeSource{ 254 Local: &v1.LocalVolumeSource{ 255 // Not needed because we don't need to detach local device from the host. 256 Path: "", 257 }, 258 }, 259 VolumeMode: &block, 260 }, 261 } 262 263 return volume.NewSpecFromPersistentVolume(localVolume, false), nil 264 } 265 266 func (plugin *localVolumePlugin) generateBlockDeviceBaseGlobalPath() string { 267 return filepath.Join(plugin.host.GetPluginDir(localVolumePluginName), util.MountsInGlobalPDPath) 268 } 269 270 func (plugin *localVolumePlugin) getGlobalLocalPath(spec *volume.Spec) (string, error) { 271 if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 { 272 return "", fmt.Errorf("local volume source is nil or local path is not set") 273 } 274 275 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 276 if !ok { 277 return "", fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 278 } 279 280 fileType, err := kvh.GetHostUtil().GetFileType(spec.PersistentVolume.Spec.Local.Path) 281 if err != nil { 282 return "", err 283 } 284 switch fileType { 285 case hostutil.FileTypeDirectory: 286 return spec.PersistentVolume.Spec.Local.Path, nil 287 case hostutil.FileTypeBlockDev: 288 return filepath.Join(plugin.generateBlockDeviceBaseGlobalPath(), spec.Name()), nil 289 default: 290 return "", fmt.Errorf("only directory and block device are supported") 291 } 292 } 293 294 var _ volume.DeviceMountableVolumePlugin = &localVolumePlugin{} 295 296 type deviceMounter struct { 297 plugin *localVolumePlugin 298 mounter *mount.SafeFormatAndMount 299 hostUtil hostutil.HostUtils 300 } 301 302 var _ volume.DeviceMounter = &deviceMounter{} 303 304 func (plugin *localVolumePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { 305 return true, nil 306 } 307 308 func (plugin *localVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) { 309 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 310 if !ok { 311 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 312 } 313 return &deviceMounter{ 314 plugin: plugin, 315 mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), 316 hostUtil: kvh.GetHostUtil(), 317 }, nil 318 } 319 320 func (dm *deviceMounter) mountLocalBlockDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { 321 klog.V(4).Infof("local: mounting device %s to %s", devicePath, deviceMountPath) 322 notMnt, err := dm.mounter.IsLikelyNotMountPoint(deviceMountPath) 323 if err != nil { 324 if os.IsNotExist(err) { 325 if err := os.MkdirAll(deviceMountPath, 0750); err != nil { 326 return err 327 } 328 notMnt = true 329 } else { 330 return err 331 } 332 } 333 if !notMnt { 334 return nil 335 } 336 fstype, err := getVolumeSourceFSType(spec) 337 if err != nil { 338 return err 339 } 340 341 ro, err := getVolumeSourceReadOnly(spec) 342 if err != nil { 343 return err 344 } 345 options := []string{} 346 if ro { 347 options = append(options, "ro") 348 } 349 mountOptions := util.MountOptionFromSpec(spec, options...) 350 err = dm.mounter.FormatAndMount(devicePath, deviceMountPath, fstype, mountOptions) 351 if err != nil { 352 if rmErr := os.Remove(deviceMountPath); rmErr != nil { 353 klog.Warningf("local: failed to remove %s: %v", deviceMountPath, rmErr) 354 } 355 return fmt.Errorf("local: failed to mount device %s at %s (fstype: %s), error %w", devicePath, deviceMountPath, fstype, err) 356 } 357 klog.V(3).Infof("local: successfully mount device %s at %s (fstype: %s)", devicePath, deviceMountPath, fstype) 358 return nil 359 } 360 361 func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error { 362 if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 { 363 return fmt.Errorf("local volume source is nil or local path is not set") 364 } 365 fileType, err := dm.hostUtil.GetFileType(spec.PersistentVolume.Spec.Local.Path) 366 if err != nil { 367 return err 368 } 369 370 switch fileType { 371 case hostutil.FileTypeBlockDev: 372 // local volume plugin does not implement AttachableVolumePlugin interface, so set devicePath to Path in PV spec directly 373 return dm.mountLocalBlockDevice(spec, spec.PersistentVolume.Spec.Local.Path, deviceMountPath) 374 case hostutil.FileTypeDirectory: 375 // if the given local volume path is of already filesystem directory, return directly 376 return nil 377 default: 378 return fmt.Errorf("only directory and block device are supported") 379 } 380 } 381 382 func (plugin *localVolumePlugin) RequiresFSResize() bool { 383 return true 384 } 385 386 func (plugin *localVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) { 387 fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec) 388 if err != nil { 389 return false, fmt.Errorf("error checking VolumeMode: %v", err) 390 } 391 if !fsVolume { 392 return true, nil 393 } 394 395 localDevicePath := resizeOptions.VolumeSpec.PersistentVolume.Spec.Local.Path 396 397 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 398 if !ok { 399 return false, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 400 } 401 402 fileType, err := kvh.GetHostUtil().GetFileType(localDevicePath) 403 if err != nil { 404 return false, err 405 } 406 407 switch fileType { 408 case hostutil.FileTypeBlockDev: 409 _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), localDevicePath, resizeOptions.DeviceMountPath) 410 if err != nil { 411 return false, err 412 } 413 return true, nil 414 case hostutil.FileTypeDirectory: 415 // if the given local volume path is of already filesystem directory, return directly because 416 // we do not want to prevent mount operation from succeeding. 417 klog.InfoS("Expansion of directory based local volumes is NO-OP", "localVolumePath", localDevicePath) 418 return true, nil 419 default: 420 return false, fmt.Errorf("only directory and block device are supported") 421 } 422 } 423 424 func getVolumeSourceFSType(spec *volume.Spec) (string, error) { 425 if spec.PersistentVolume != nil && 426 spec.PersistentVolume.Spec.Local != nil { 427 if spec.PersistentVolume.Spec.Local.FSType != nil { 428 return *spec.PersistentVolume.Spec.Local.FSType, nil 429 } 430 // if the FSType is not set in local PV spec, setting it to default ("ext4") 431 return defaultFSType, nil 432 } 433 434 return "", fmt.Errorf("spec does not reference a Local volume type") 435 } 436 437 func getVolumeSourceReadOnly(spec *volume.Spec) (bool, error) { 438 if spec.PersistentVolume != nil && 439 spec.PersistentVolume.Spec.Local != nil { 440 // local volumes used as a PersistentVolume gets the ReadOnly flag indirectly through 441 // the persistent-claim volume used to mount the PV 442 return spec.ReadOnly, nil 443 } 444 445 return false, fmt.Errorf("spec does not reference a Local volume type") 446 } 447 448 func (dm *deviceMounter) GetDeviceMountPath(spec *volume.Spec) (string, error) { 449 return dm.plugin.getGlobalLocalPath(spec) 450 } 451 452 func (plugin *localVolumePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { 453 return &deviceMounter{ 454 plugin: plugin, 455 mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), 456 }, nil 457 } 458 459 func (plugin *localVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { 460 mounter := plugin.host.GetMounter(plugin.GetPluginName()) 461 return mounter.GetMountRefs(deviceMountPath) 462 } 463 464 var _ volume.DeviceUnmounter = &deviceMounter{} 465 466 func (dm *deviceMounter) UnmountDevice(deviceMountPath string) error { 467 // If the local PV is a block device, 468 // The deviceMountPath is generated to the format like :/var/lib/kubelet/plugins/kubernetes.io/local-volume/mounts/localpv.spec.Name; 469 // If it is a filesystem directory, then the deviceMountPath is set directly to pvSpec.Local.Path 470 // We only need to unmount block device here, so we need to check if the deviceMountPath passed here 471 // has base mount path: /var/lib/kubelet/plugins/kubernetes.io/local-volume/mounts 472 basemountPath := dm.plugin.generateBlockDeviceBaseGlobalPath() 473 if mount.PathWithinBase(deviceMountPath, basemountPath) { 474 return mount.CleanupMountPoint(deviceMountPath, dm.mounter, false) 475 } 476 477 return nil 478 } 479 480 // Local volumes represent a local directory on a node. 481 // The directory at the globalPath will be bind-mounted to the pod's directory 482 type localVolume struct { 483 volName string 484 pod *v1.Pod 485 podUID types.UID 486 // Global path to the volume 487 globalPath string 488 // Mounter interface that provides system calls to mount the global path to the pod local path. 489 mounter mount.Interface 490 hostUtil hostutil.HostUtils 491 plugin *localVolumePlugin 492 volume.MetricsProvider 493 } 494 495 func (l *localVolume) GetPath() string { 496 return l.plugin.host.GetPodVolumeDir(l.podUID, utilstrings.EscapeQualifiedName(localVolumePluginName), l.volName) 497 } 498 499 type localVolumeMounter struct { 500 *localVolume 501 readOnly bool 502 mountOptions []string 503 } 504 505 var _ volume.Mounter = &localVolumeMounter{} 506 507 func (m *localVolumeMounter) GetAttributes() volume.Attributes { 508 return volume.Attributes{ 509 ReadOnly: m.readOnly, 510 Managed: !m.readOnly, 511 SELinuxRelabel: true, 512 } 513 } 514 515 // SetUp bind mounts the directory to the volume path 516 func (m *localVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 517 return m.SetUpAt(m.GetPath(), mounterArgs) 518 } 519 520 // SetUpAt bind mounts the directory to the volume path and sets up volume ownership 521 func (m *localVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 522 m.plugin.volumeLocks.LockKey(m.globalPath) 523 defer m.plugin.volumeLocks.UnlockKey(m.globalPath) 524 525 if m.globalPath == "" { 526 return fmt.Errorf("LocalVolume volume %q path is empty", m.volName) 527 } 528 529 err := validation.ValidatePathNoBacksteps(m.globalPath) 530 if err != nil { 531 return fmt.Errorf("invalid path: %s %v", m.globalPath, err) 532 } 533 534 notMnt, err := mount.IsNotMountPoint(m.mounter, dir) 535 klog.V(4).Infof("LocalVolume mount setup: PodDir(%s) VolDir(%s) Mounted(%t) Error(%v), ReadOnly(%t)", dir, m.globalPath, !notMnt, err, m.readOnly) 536 if err != nil && !os.IsNotExist(err) { 537 klog.Errorf("cannot validate mount point: %s %v", dir, err) 538 return err 539 } 540 541 if !notMnt { 542 return nil 543 } 544 refs, err := m.mounter.GetMountRefs(m.globalPath) 545 if mounterArgs.FsGroup != nil { 546 if err != nil { 547 klog.Errorf("cannot collect mounting information: %s %v", m.globalPath, err) 548 return err 549 } 550 551 // Only count mounts from other pods 552 refs = m.filterPodMounts(refs) 553 if len(refs) > 0 { 554 fsGroupNew := int64(*mounterArgs.FsGroup) 555 _, fsGroupOld, err := m.hostUtil.GetOwner(m.globalPath) 556 if err != nil { 557 return fmt.Errorf("failed to check fsGroup for %s (%v)", m.globalPath, err) 558 } 559 if fsGroupNew != fsGroupOld { 560 m.plugin.recorder.Eventf(m.pod, v1.EventTypeWarning, events.WarnAlreadyMountedVolume, "The requested fsGroup is %d, but the volume %s has GID %d. The volume may not be shareable.", fsGroupNew, m.volName, fsGroupOld) 561 } 562 } 563 564 } 565 566 if runtime.GOOS != "windows" { 567 // skip below MkdirAll for windows since the "bind mount" logic is implemented differently in mount_wiondows.go 568 if err := os.MkdirAll(dir, 0750); err != nil { 569 klog.Errorf("mkdir failed on disk %s (%v)", dir, err) 570 return err 571 } 572 } 573 // Perform a bind mount to the full path to allow duplicate mounts of the same volume. 574 options := []string{"bind"} 575 if m.readOnly { 576 options = append(options, "ro") 577 } 578 mountOptions := util.JoinMountOptions(options, m.mountOptions) 579 580 klog.V(4).Infof("attempting to mount %s", dir) 581 globalPath := util.MakeAbsolutePath(runtime.GOOS, m.globalPath) 582 err = m.mounter.MountSensitiveWithoutSystemd(globalPath, dir, "", mountOptions, nil) 583 if err != nil { 584 klog.Errorf("Mount of volume %s failed: %v", dir, err) 585 notMnt, mntErr := mount.IsNotMountPoint(m.mounter, dir) 586 if mntErr != nil { 587 klog.Errorf("IsNotMountPoint check failed: %v", mntErr) 588 return err 589 } 590 if !notMnt { 591 if mntErr = m.mounter.Unmount(dir); mntErr != nil { 592 klog.Errorf("Failed to unmount: %v", mntErr) 593 return err 594 } 595 notMnt, mntErr = mount.IsNotMountPoint(m.mounter, dir) 596 if mntErr != nil { 597 klog.Errorf("IsNotMountPoint check failed: %v", mntErr) 598 return err 599 } 600 if !notMnt { 601 // This is very odd, we don't expect it. We'll try again next sync loop. 602 klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir) 603 return err 604 } 605 } 606 if rmErr := os.Remove(dir); rmErr != nil { 607 klog.Warningf("failed to remove %s: %v", dir, rmErr) 608 } 609 return err 610 } 611 if !m.readOnly { 612 // Volume owner will be written only once on the first volume mount 613 if len(refs) == 0 { 614 return volume.SetVolumeOwnership(m, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(m.plugin, nil)) 615 } 616 } 617 return nil 618 } 619 620 // filterPodMounts only returns mount paths inside the kubelet pod directory 621 func (m *localVolumeMounter) filterPodMounts(refs []string) []string { 622 filtered := []string{} 623 for _, r := range refs { 624 if strings.HasPrefix(r, m.plugin.host.GetPodsDir()+string(os.PathSeparator)) { 625 filtered = append(filtered, r) 626 } 627 } 628 return filtered 629 } 630 631 type localVolumeUnmounter struct { 632 *localVolume 633 } 634 635 var _ volume.Unmounter = &localVolumeUnmounter{} 636 637 // TearDown unmounts the bind mount 638 func (u *localVolumeUnmounter) TearDown() error { 639 return u.TearDownAt(u.GetPath()) 640 } 641 642 // TearDownAt unmounts the bind mount 643 func (u *localVolumeUnmounter) TearDownAt(dir string) error { 644 klog.V(4).Infof("Unmounting volume %q at path %q\n", u.volName, dir) 645 return mount.CleanupMountPoint(dir, u.mounter, true) /* extensiveMountPointCheck = true */ 646 } 647 648 // localVolumeMapper implements the BlockVolumeMapper interface for local volumes. 649 type localVolumeMapper struct { 650 *localVolume 651 readOnly bool 652 } 653 654 var _ volume.BlockVolumeMapper = &localVolumeMapper{} 655 var _ volume.CustomBlockVolumeMapper = &localVolumeMapper{} 656 657 // SetUpDevice prepares the volume to the node by the plugin specific way. 658 func (m *localVolumeMapper) SetUpDevice() (string, error) { 659 return "", nil 660 } 661 662 // MapPodDevice provides physical device path for the local PV. 663 func (m *localVolumeMapper) MapPodDevice() (string, error) { 664 globalPath := util.MakeAbsolutePath(runtime.GOOS, m.globalPath) 665 klog.V(4).Infof("MapPodDevice returning path %s", globalPath) 666 return globalPath, nil 667 } 668 669 // GetStagingPath returns 670 func (m *localVolumeMapper) GetStagingPath() string { 671 return "" 672 } 673 674 // SupportsMetrics returns true for SupportsMetrics as it initializes the 675 // MetricsProvider. 676 func (m *localVolumeMapper) SupportsMetrics() bool { 677 return true 678 } 679 680 // localVolumeUnmapper implements the BlockVolumeUnmapper interface for local volumes. 681 type localVolumeUnmapper struct { 682 *localVolume 683 volume.MetricsNil 684 } 685 686 var _ volume.BlockVolumeUnmapper = &localVolumeUnmapper{} 687 688 // GetGlobalMapPath returns global map path and error. 689 // path: plugins/kubernetes.io/kubernetes.io/local-volume/volumeDevices/{volumeName} 690 func (l *localVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) { 691 return filepath.Join(l.plugin.host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedName(localVolumePluginName)), 692 l.volName), nil 693 } 694 695 // GetPodDeviceMapPath returns pod device map path and volume name. 696 // path: pods/{podUid}/volumeDevices/kubernetes.io~local-volume 697 // volName: local-pv-ff0d6d4 698 func (l *localVolume) GetPodDeviceMapPath() (string, string) { 699 return l.plugin.host.GetPodVolumeDeviceDir(l.podUID, 700 utilstrings.EscapeQualifiedName(localVolumePluginName)), l.volName 701 }