1
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
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
79 return spec.Name(), nil
80 }
81
82 func (plugin *localVolumePlugin) CanSupport(spec *volume.Spec) bool {
83
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) SupportsBulkVolumeVerification() bool {
96 return false
97 }
98
99 func (plugin *localVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
100 return false, nil
101 }
102
103 func (plugin *localVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
104
105 return []v1.PersistentVolumeAccessMode{
106 v1.ReadWriteOnce,
107 }
108 }
109
110 func getVolumeSource(spec *volume.Spec) (*v1.LocalVolumeSource, bool, error) {
111 if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Local != nil {
112 return spec.PersistentVolume.Spec.Local, spec.ReadOnly, nil
113 }
114
115 return nil, false, fmt.Errorf("Spec does not reference a Local volume type")
116 }
117
118 func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
119 _, readOnly, err := getVolumeSource(spec)
120 if err != nil {
121 return nil, err
122 }
123
124 globalLocalPath, err := plugin.getGlobalLocalPath(spec)
125 if err != nil {
126 return nil, err
127 }
128
129 kvh, ok := plugin.host.(volume.KubeletVolumeHost)
130 if !ok {
131 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
132 }
133
134 return &localVolumeMounter{
135 localVolume: &localVolume{
136 pod: pod,
137 podUID: pod.UID,
138 volName: spec.Name(),
139 mounter: plugin.host.GetMounter(plugin.GetPluginName()),
140 hostUtil: kvh.GetHostUtil(),
141 plugin: plugin,
142 globalPath: globalLocalPath,
143 MetricsProvider: volume.NewMetricsStatFS(plugin.host.GetPodVolumeDir(pod.UID, utilstrings.EscapeQualifiedName(localVolumePluginName), spec.Name())),
144 },
145 mountOptions: util.MountOptionFromSpec(spec),
146 readOnly: readOnly,
147 }, nil
148
149 }
150
151 func (plugin *localVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
152 return &localVolumeUnmounter{
153 localVolume: &localVolume{
154 podUID: podUID,
155 volName: volName,
156 mounter: plugin.host.GetMounter(plugin.GetPluginName()),
157 plugin: plugin,
158 },
159 }, nil
160 }
161
162 func (plugin *localVolumePlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod,
163 _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) {
164 volumeSource, readOnly, err := getVolumeSource(spec)
165 if err != nil {
166 return nil, err
167 }
168
169 mapper := &localVolumeMapper{
170 localVolume: &localVolume{
171 podUID: pod.UID,
172 volName: spec.Name(),
173 globalPath: volumeSource.Path,
174 plugin: plugin,
175 },
176 readOnly: readOnly,
177 }
178
179 blockPath, err := mapper.GetGlobalMapPath(spec)
180 if err != nil {
181 return nil, fmt.Errorf("failed to get device path: %v", err)
182 }
183 mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(pod.UID)))
184
185 return mapper, nil
186 }
187
188 func (plugin *localVolumePlugin) NewBlockVolumeUnmapper(volName string,
189 podUID types.UID) (volume.BlockVolumeUnmapper, error) {
190 return &localVolumeUnmapper{
191 localVolume: &localVolume{
192 podUID: podUID,
193 volName: volName,
194 plugin: plugin,
195 },
196 }, nil
197 }
198
199
200 func (plugin *localVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
201 fs := v1.PersistentVolumeFilesystem
202
203
204
205
206
207
208 var path string
209 mounter := plugin.host.GetMounter(plugin.GetPluginName())
210 refs, err := mounter.GetMountRefs(mountPath)
211 if err != nil {
212 return volume.ReconstructedVolume{}, err
213 }
214 baseMountPath := plugin.generateBlockDeviceBaseGlobalPath()
215 for _, ref := range refs {
216 if mount.PathWithinBase(ref, baseMountPath) {
217
218
219
220
221
222 path, _, err = mount.GetDeviceNameFromMount(mounter, ref)
223 if err != nil {
224 return volume.ReconstructedVolume{}, err
225 }
226 klog.V(4).Infof("local: reconstructing volume %q (pod volume mount: %q) with device %q", volumeName, mountPath, path)
227 break
228 }
229 }
230 localVolume := &v1.PersistentVolume{
231 ObjectMeta: metav1.ObjectMeta{
232 Name: volumeName,
233 },
234 Spec: v1.PersistentVolumeSpec{
235 PersistentVolumeSource: v1.PersistentVolumeSource{
236 Local: &v1.LocalVolumeSource{
237 Path: path,
238 },
239 },
240 VolumeMode: &fs,
241 },
242 }
243 return volume.ReconstructedVolume{
244 Spec: volume.NewSpecFromPersistentVolume(localVolume, false),
245 }, nil
246 }
247
248 func (plugin *localVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName,
249 mapPath string) (*volume.Spec, error) {
250 block := v1.PersistentVolumeBlock
251
252 localVolume := &v1.PersistentVolume{
253 ObjectMeta: metav1.ObjectMeta{
254 Name: volumeName,
255 },
256 Spec: v1.PersistentVolumeSpec{
257 PersistentVolumeSource: v1.PersistentVolumeSource{
258 Local: &v1.LocalVolumeSource{
259
260 Path: "",
261 },
262 },
263 VolumeMode: &block,
264 },
265 }
266
267 return volume.NewSpecFromPersistentVolume(localVolume, false), nil
268 }
269
270 func (plugin *localVolumePlugin) generateBlockDeviceBaseGlobalPath() string {
271 return filepath.Join(plugin.host.GetPluginDir(localVolumePluginName), util.MountsInGlobalPDPath)
272 }
273
274 func (plugin *localVolumePlugin) getGlobalLocalPath(spec *volume.Spec) (string, error) {
275 if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 {
276 return "", fmt.Errorf("local volume source is nil or local path is not set")
277 }
278
279 kvh, ok := plugin.host.(volume.KubeletVolumeHost)
280 if !ok {
281 return "", fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
282 }
283
284 fileType, err := kvh.GetHostUtil().GetFileType(spec.PersistentVolume.Spec.Local.Path)
285 if err != nil {
286 return "", err
287 }
288 switch fileType {
289 case hostutil.FileTypeDirectory:
290 return spec.PersistentVolume.Spec.Local.Path, nil
291 case hostutil.FileTypeBlockDev:
292 return filepath.Join(plugin.generateBlockDeviceBaseGlobalPath(), spec.Name()), nil
293 default:
294 return "", fmt.Errorf("only directory and block device are supported")
295 }
296 }
297
298 var _ volume.DeviceMountableVolumePlugin = &localVolumePlugin{}
299
300 type deviceMounter struct {
301 plugin *localVolumePlugin
302 mounter *mount.SafeFormatAndMount
303 hostUtil hostutil.HostUtils
304 }
305
306 var _ volume.DeviceMounter = &deviceMounter{}
307
308 func (plugin *localVolumePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
309 return true, nil
310 }
311
312 func (plugin *localVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
313 kvh, ok := plugin.host.(volume.KubeletVolumeHost)
314 if !ok {
315 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
316 }
317 return &deviceMounter{
318 plugin: plugin,
319 mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
320 hostUtil: kvh.GetHostUtil(),
321 }, nil
322 }
323
324 func (dm *deviceMounter) mountLocalBlockDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
325 klog.V(4).Infof("local: mounting device %s to %s", devicePath, deviceMountPath)
326 notMnt, err := dm.mounter.IsLikelyNotMountPoint(deviceMountPath)
327 if err != nil {
328 if os.IsNotExist(err) {
329 if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
330 return err
331 }
332 notMnt = true
333 } else {
334 return err
335 }
336 }
337 if !notMnt {
338 return nil
339 }
340 fstype, err := getVolumeSourceFSType(spec)
341 if err != nil {
342 return err
343 }
344
345 ro, err := getVolumeSourceReadOnly(spec)
346 if err != nil {
347 return err
348 }
349 options := []string{}
350 if ro {
351 options = append(options, "ro")
352 }
353 mountOptions := util.MountOptionFromSpec(spec, options...)
354 err = dm.mounter.FormatAndMount(devicePath, deviceMountPath, fstype, mountOptions)
355 if err != nil {
356 if rmErr := os.Remove(deviceMountPath); rmErr != nil {
357 klog.Warningf("local: failed to remove %s: %v", deviceMountPath, rmErr)
358 }
359 return fmt.Errorf("local: failed to mount device %s at %s (fstype: %s), error %w", devicePath, deviceMountPath, fstype, err)
360 }
361 klog.V(3).Infof("local: successfully mount device %s at %s (fstype: %s)", devicePath, deviceMountPath, fstype)
362 return nil
363 }
364
365 func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
366 if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 {
367 return fmt.Errorf("local volume source is nil or local path is not set")
368 }
369 fileType, err := dm.hostUtil.GetFileType(spec.PersistentVolume.Spec.Local.Path)
370 if err != nil {
371 return err
372 }
373
374 switch fileType {
375 case hostutil.FileTypeBlockDev:
376
377 return dm.mountLocalBlockDevice(spec, spec.PersistentVolume.Spec.Local.Path, deviceMountPath)
378 case hostutil.FileTypeDirectory:
379
380 return nil
381 default:
382 return fmt.Errorf("only directory and block device are supported")
383 }
384 }
385
386 func (plugin *localVolumePlugin) RequiresFSResize() bool {
387 return true
388 }
389
390 func (plugin *localVolumePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
391 fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec)
392 if err != nil {
393 return false, fmt.Errorf("error checking VolumeMode: %v", err)
394 }
395 if !fsVolume {
396 return true, nil
397 }
398
399 localDevicePath := resizeOptions.VolumeSpec.PersistentVolume.Spec.Local.Path
400
401 kvh, ok := plugin.host.(volume.KubeletVolumeHost)
402 if !ok {
403 return false, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
404 }
405
406 fileType, err := kvh.GetHostUtil().GetFileType(localDevicePath)
407 if err != nil {
408 return false, err
409 }
410
411 switch fileType {
412 case hostutil.FileTypeBlockDev:
413 _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), localDevicePath, resizeOptions.DeviceMountPath)
414 if err != nil {
415 return false, err
416 }
417 return true, nil
418 case hostutil.FileTypeDirectory:
419
420
421 klog.InfoS("Expansion of directory based local volumes is NO-OP", "localVolumePath", localDevicePath)
422 return true, nil
423 default:
424 return false, fmt.Errorf("only directory and block device are supported")
425 }
426 }
427
428 func getVolumeSourceFSType(spec *volume.Spec) (string, error) {
429 if spec.PersistentVolume != nil &&
430 spec.PersistentVolume.Spec.Local != nil {
431 if spec.PersistentVolume.Spec.Local.FSType != nil {
432 return *spec.PersistentVolume.Spec.Local.FSType, nil
433 }
434
435 return defaultFSType, nil
436 }
437
438 return "", fmt.Errorf("spec does not reference a Local volume type")
439 }
440
441 func getVolumeSourceReadOnly(spec *volume.Spec) (bool, error) {
442 if spec.PersistentVolume != nil &&
443 spec.PersistentVolume.Spec.Local != nil {
444
445
446 return spec.ReadOnly, nil
447 }
448
449 return false, fmt.Errorf("spec does not reference a Local volume type")
450 }
451
452 func (dm *deviceMounter) GetDeviceMountPath(spec *volume.Spec) (string, error) {
453 return dm.plugin.getGlobalLocalPath(spec)
454 }
455
456 func (plugin *localVolumePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
457 return &deviceMounter{
458 plugin: plugin,
459 mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
460 }, nil
461 }
462
463 func (plugin *localVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
464 mounter := plugin.host.GetMounter(plugin.GetPluginName())
465 return mounter.GetMountRefs(deviceMountPath)
466 }
467
468 var _ volume.DeviceUnmounter = &deviceMounter{}
469
470 func (dm *deviceMounter) UnmountDevice(deviceMountPath string) error {
471
472
473
474
475
476 basemountPath := dm.plugin.generateBlockDeviceBaseGlobalPath()
477 if mount.PathWithinBase(deviceMountPath, basemountPath) {
478 return mount.CleanupMountPoint(deviceMountPath, dm.mounter, false)
479 }
480
481 return nil
482 }
483
484
485
486 type localVolume struct {
487 volName string
488 pod *v1.Pod
489 podUID types.UID
490
491 globalPath string
492
493 mounter mount.Interface
494 hostUtil hostutil.HostUtils
495 plugin *localVolumePlugin
496 volume.MetricsProvider
497 }
498
499 func (l *localVolume) GetPath() string {
500 return l.plugin.host.GetPodVolumeDir(l.podUID, utilstrings.EscapeQualifiedName(localVolumePluginName), l.volName)
501 }
502
503 type localVolumeMounter struct {
504 *localVolume
505 readOnly bool
506 mountOptions []string
507 }
508
509 var _ volume.Mounter = &localVolumeMounter{}
510
511 func (m *localVolumeMounter) GetAttributes() volume.Attributes {
512 return volume.Attributes{
513 ReadOnly: m.readOnly,
514 Managed: !m.readOnly,
515 SELinuxRelabel: true,
516 }
517 }
518
519
520 func (m *localVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
521 return m.SetUpAt(m.GetPath(), mounterArgs)
522 }
523
524
525 func (m *localVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
526 m.plugin.volumeLocks.LockKey(m.globalPath)
527 defer m.plugin.volumeLocks.UnlockKey(m.globalPath)
528
529 if m.globalPath == "" {
530 return fmt.Errorf("LocalVolume volume %q path is empty", m.volName)
531 }
532
533 err := validation.ValidatePathNoBacksteps(m.globalPath)
534 if err != nil {
535 return fmt.Errorf("invalid path: %s %v", m.globalPath, err)
536 }
537
538 notMnt, err := mount.IsNotMountPoint(m.mounter, dir)
539 klog.V(4).Infof("LocalVolume mount setup: PodDir(%s) VolDir(%s) Mounted(%t) Error(%v), ReadOnly(%t)", dir, m.globalPath, !notMnt, err, m.readOnly)
540 if err != nil && !os.IsNotExist(err) {
541 klog.Errorf("cannot validate mount point: %s %v", dir, err)
542 return err
543 }
544
545 if !notMnt {
546 return nil
547 }
548 refs, err := m.mounter.GetMountRefs(m.globalPath)
549 if mounterArgs.FsGroup != nil {
550 if err != nil {
551 klog.Errorf("cannot collect mounting information: %s %v", m.globalPath, err)
552 return err
553 }
554
555
556 refs = m.filterPodMounts(refs)
557 if len(refs) > 0 {
558 fsGroupNew := int64(*mounterArgs.FsGroup)
559 _, fsGroupOld, err := m.hostUtil.GetOwner(m.globalPath)
560 if err != nil {
561 return fmt.Errorf("failed to check fsGroup for %s (%v)", m.globalPath, err)
562 }
563 if fsGroupNew != fsGroupOld {
564 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)
565 }
566 }
567
568 }
569
570 if runtime.GOOS != "windows" {
571
572 if err := os.MkdirAll(dir, 0750); err != nil {
573 klog.Errorf("mkdir failed on disk %s (%v)", dir, err)
574 return err
575 }
576 }
577
578 options := []string{"bind"}
579 if m.readOnly {
580 options = append(options, "ro")
581 }
582 mountOptions := util.JoinMountOptions(options, m.mountOptions)
583
584 klog.V(4).Infof("attempting to mount %s", dir)
585 globalPath := util.MakeAbsolutePath(runtime.GOOS, m.globalPath)
586 err = m.mounter.MountSensitiveWithoutSystemd(globalPath, dir, "", mountOptions, nil)
587 if err != nil {
588 klog.Errorf("Mount of volume %s failed: %v", dir, err)
589 notMnt, mntErr := mount.IsNotMountPoint(m.mounter, dir)
590 if mntErr != nil {
591 klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
592 return err
593 }
594 if !notMnt {
595 if mntErr = m.mounter.Unmount(dir); mntErr != nil {
596 klog.Errorf("Failed to unmount: %v", mntErr)
597 return err
598 }
599 notMnt, mntErr = mount.IsNotMountPoint(m.mounter, dir)
600 if mntErr != nil {
601 klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
602 return err
603 }
604 if !notMnt {
605
606 klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
607 return err
608 }
609 }
610 if rmErr := os.Remove(dir); rmErr != nil {
611 klog.Warningf("failed to remove %s: %v", dir, rmErr)
612 }
613 return err
614 }
615 if !m.readOnly {
616
617 if len(refs) == 0 {
618 return volume.SetVolumeOwnership(m, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(m.plugin, nil))
619 }
620 }
621 return nil
622 }
623
624
625 func (m *localVolumeMounter) filterPodMounts(refs []string) []string {
626 filtered := []string{}
627 for _, r := range refs {
628 if strings.HasPrefix(r, m.plugin.host.GetPodsDir()+string(os.PathSeparator)) {
629 filtered = append(filtered, r)
630 }
631 }
632 return filtered
633 }
634
635 type localVolumeUnmounter struct {
636 *localVolume
637 }
638
639 var _ volume.Unmounter = &localVolumeUnmounter{}
640
641
642 func (u *localVolumeUnmounter) TearDown() error {
643 return u.TearDownAt(u.GetPath())
644 }
645
646
647 func (u *localVolumeUnmounter) TearDownAt(dir string) error {
648 klog.V(4).Infof("Unmounting volume %q at path %q\n", u.volName, dir)
649 return mount.CleanupMountPoint(dir, u.mounter, true)
650 }
651
652
653 type localVolumeMapper struct {
654 *localVolume
655 readOnly bool
656 }
657
658 var _ volume.BlockVolumeMapper = &localVolumeMapper{}
659 var _ volume.CustomBlockVolumeMapper = &localVolumeMapper{}
660
661
662 func (m *localVolumeMapper) SetUpDevice() (string, error) {
663 return "", nil
664 }
665
666
667 func (m *localVolumeMapper) MapPodDevice() (string, error) {
668 globalPath := util.MakeAbsolutePath(runtime.GOOS, m.globalPath)
669 klog.V(4).Infof("MapPodDevice returning path %s", globalPath)
670 return globalPath, nil
671 }
672
673
674 func (m *localVolumeMapper) GetStagingPath() string {
675 return ""
676 }
677
678
679
680 func (m *localVolumeMapper) SupportsMetrics() bool {
681 return true
682 }
683
684
685 type localVolumeUnmapper struct {
686 *localVolume
687 volume.MetricsNil
688 }
689
690 var _ volume.BlockVolumeUnmapper = &localVolumeUnmapper{}
691
692
693
694 func (l *localVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) {
695 return filepath.Join(l.plugin.host.GetVolumeDevicePluginDir(utilstrings.EscapeQualifiedName(localVolumePluginName)),
696 l.volName), nil
697 }
698
699
700
701
702 func (l *localVolume) GetPodDeviceMapPath() (string, string) {
703 return l.plugin.host.GetPodVolumeDeviceDir(l.podUID,
704 utilstrings.EscapeQualifiedName(localVolumePluginName)), l.volName
705 }
706
View as plain text