k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/nfs/nfs.go (about)

     1  /*
     2  Copyright 2014 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 nfs
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"time"
    23  
    24  	netutil "k8s.io/utils/net"
    25  
    26  	"k8s.io/klog/v2"
    27  	"k8s.io/mount-utils"
    28  	utilstrings "k8s.io/utils/strings"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/kubernetes/pkg/volume"
    34  	"k8s.io/kubernetes/pkg/volume/util"
    35  	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
    36  )
    37  
    38  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    39  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(nfsPluginName), volName)
    40  }
    41  
    42  // ProbeVolumePlugins is the primary entrypoint for volume plugins.
    43  // This is the primary entrypoint for volume plugins.
    44  // The volumeConfig arg provides the ability to configure recycler behavior.  It is implemented as a pointer to allow nils.
    45  // The nfsPlugin is used to store the volumeConfig and give it, when needed, to the func that creates NFS Recyclers.
    46  // Tests that exercise recycling should not use this func but instead use ProbeRecyclablePlugins() to override default behavior.
    47  func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
    48  	return []volume.VolumePlugin{
    49  		&nfsPlugin{
    50  			host:   nil,
    51  			config: volumeConfig,
    52  		},
    53  	}
    54  }
    55  
    56  type nfsPlugin struct {
    57  	host   volume.VolumeHost
    58  	config volume.VolumeConfig
    59  }
    60  
    61  var _ volume.VolumePlugin = &nfsPlugin{}
    62  var _ volume.PersistentVolumePlugin = &nfsPlugin{}
    63  var _ volume.RecyclableVolumePlugin = &nfsPlugin{}
    64  
    65  const (
    66  	nfsPluginName  = "kubernetes.io/nfs"
    67  	unMountTimeout = time.Minute
    68  )
    69  
    70  func (plugin *nfsPlugin) Init(host volume.VolumeHost) error {
    71  	plugin.host = host
    72  	return nil
    73  }
    74  
    75  func (plugin *nfsPlugin) GetPluginName() string {
    76  	return nfsPluginName
    77  }
    78  
    79  func (plugin *nfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    80  	volumeSource, _, err := getVolumeSource(spec)
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  
    85  	return fmt.Sprintf(
    86  		"%v/%v",
    87  		volumeSource.Server,
    88  		volumeSource.Path), nil
    89  }
    90  
    91  func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
    92  	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.NFS != nil) ||
    93  		(spec.Volume != nil && spec.Volume.NFS != nil)
    94  }
    95  
    96  func (plugin *nfsPlugin) RequiresRemount(spec *volume.Spec) bool {
    97  	return false
    98  }
    99  
   100  func (plugin *nfsPlugin) SupportsMountOption() bool {
   101  	return true
   102  }
   103  
   104  func (plugin *nfsPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
   105  	return false, nil
   106  }
   107  
   108  func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
   109  	return []v1.PersistentVolumeAccessMode{
   110  		v1.ReadWriteOnce,
   111  		v1.ReadOnlyMany,
   112  		v1.ReadWriteMany,
   113  	}
   114  }
   115  
   116  func (plugin *nfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
   117  	return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
   118  }
   119  
   120  func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
   121  	source, readOnly, err := getVolumeSource(spec)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	return &nfsMounter{
   126  		nfs: &nfs{
   127  			volName:         spec.Name(),
   128  			mounter:         mounter,
   129  			pod:             pod,
   130  			plugin:          plugin,
   131  			MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
   132  		},
   133  		server:       getServerFromSource(source),
   134  		exportPath:   source.Path,
   135  		readOnly:     readOnly,
   136  		mountOptions: util.MountOptionFromSpec(spec),
   137  	}, nil
   138  }
   139  
   140  func (plugin *nfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   141  	return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
   142  }
   143  
   144  func (plugin *nfsPlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
   145  	return &nfsUnmounter{&nfs{
   146  		volName:         volName,
   147  		mounter:         mounter,
   148  		pod:             &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
   149  		plugin:          plugin,
   150  		MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
   151  	}}, nil
   152  }
   153  
   154  // Recycle recycles/scrubs clean an NFS volume.
   155  // Recycle blocks until the pod has completed or any error occurs.
   156  func (plugin *nfsPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
   157  	if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.NFS == nil {
   158  		return fmt.Errorf("spec.PersistentVolumeSource.NFS is nil")
   159  	}
   160  
   161  	pod := plugin.config.RecyclerPodTemplate
   162  	timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume)
   163  	// overrides
   164  	pod.Spec.ActiveDeadlineSeconds = &timeout
   165  	pod.GenerateName = "pv-recycler-nfs-"
   166  	pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{
   167  		NFS: &v1.NFSVolumeSource{
   168  			Server: spec.PersistentVolume.Spec.NFS.Server,
   169  			Path:   spec.PersistentVolume.Spec.NFS.Path,
   170  		},
   171  	}
   172  	return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder)
   173  }
   174  
   175  func (plugin *nfsPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   176  	nfsVolume := &v1.Volume{
   177  		Name: volumeName,
   178  		VolumeSource: v1.VolumeSource{
   179  			NFS: &v1.NFSVolumeSource{
   180  				Path: volumeName,
   181  			},
   182  		},
   183  	}
   184  	return volume.ReconstructedVolume{
   185  		Spec: volume.NewSpecFromVolume(nfsVolume),
   186  	}, nil
   187  }
   188  
   189  // NFS volumes represent a bare host file or directory mount of an NFS export.
   190  type nfs struct {
   191  	volName string
   192  	pod     *v1.Pod
   193  	mounter mount.Interface
   194  	plugin  *nfsPlugin
   195  	volume.MetricsProvider
   196  }
   197  
   198  func (nfsVolume *nfs) GetPath() string {
   199  	name := nfsPluginName
   200  	return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, utilstrings.EscapeQualifiedName(name), nfsVolume.volName)
   201  }
   202  
   203  type nfsMounter struct {
   204  	*nfs
   205  	server       string
   206  	exportPath   string
   207  	readOnly     bool
   208  	mountOptions []string
   209  }
   210  
   211  var _ volume.Mounter = &nfsMounter{}
   212  
   213  func (nfsMounter *nfsMounter) GetAttributes() volume.Attributes {
   214  	return volume.Attributes{
   215  		ReadOnly:       nfsMounter.readOnly,
   216  		Managed:        false,
   217  		SELinuxRelabel: false,
   218  	}
   219  }
   220  
   221  // SetUp attaches the disk and bind mounts to the volume path.
   222  func (nfsMounter *nfsMounter) SetUp(mounterArgs volume.MounterArgs) error {
   223  	return nfsMounter.SetUpAt(nfsMounter.GetPath(), mounterArgs)
   224  }
   225  
   226  func (nfsMounter *nfsMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   227  	notMnt, err := mount.IsNotMountPoint(nfsMounter.mounter, dir)
   228  	klog.V(4).Infof("NFS mount set up: %s %v %v", dir, !notMnt, err)
   229  	if err != nil && !os.IsNotExist(err) {
   230  		return err
   231  	}
   232  	if !notMnt {
   233  		return nil
   234  	}
   235  	if err := os.MkdirAll(dir, 0750); err != nil {
   236  		return err
   237  	}
   238  	source := fmt.Sprintf("%s:%s", nfsMounter.server, nfsMounter.exportPath)
   239  	options := []string{}
   240  	if nfsMounter.readOnly {
   241  		options = append(options, "ro")
   242  	}
   243  	mountOptions := util.JoinMountOptions(nfsMounter.mountOptions, options)
   244  	err = nfsMounter.mounter.MountSensitiveWithoutSystemd(source, dir, "nfs", mountOptions, nil)
   245  	if err != nil {
   246  		notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
   247  		if mntErr != nil {
   248  			klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
   249  			return err
   250  		}
   251  		if !notMnt {
   252  			if mntErr = nfsMounter.mounter.Unmount(dir); mntErr != nil {
   253  				klog.Errorf("Failed to unmount: %v", mntErr)
   254  				return err
   255  			}
   256  			notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
   257  			if mntErr != nil {
   258  				klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
   259  				return err
   260  			}
   261  			if !notMnt {
   262  				// This is very odd, we don't expect it.  We'll try again next sync loop.
   263  				klog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
   264  				return err
   265  			}
   266  		}
   267  		os.Remove(dir)
   268  		return err
   269  	}
   270  	return nil
   271  }
   272  
   273  var _ volume.Unmounter = &nfsUnmounter{}
   274  
   275  type nfsUnmounter struct {
   276  	*nfs
   277  }
   278  
   279  func (c *nfsUnmounter) TearDown() error {
   280  	return c.TearDownAt(c.GetPath())
   281  }
   282  
   283  func (c *nfsUnmounter) TearDownAt(dir string) error {
   284  	// Use extensiveMountPointCheck to consult /proc/mounts. We can't use faster
   285  	// IsLikelyNotMountPoint (lstat()), since there may be root_squash on the
   286  	// NFS server and kubelet may not be able to do lstat/stat() there.
   287  	forceUnmounter, ok := c.mounter.(mount.MounterForceUnmounter)
   288  	if ok {
   289  		klog.V(4).Infof("Using force unmounter interface")
   290  		return mount.CleanupMountWithForce(dir, forceUnmounter, true /* extensiveMountPointCheck */, unMountTimeout)
   291  	}
   292  	return mount.CleanupMountPoint(dir, c.mounter, true /* extensiveMountPointCheck */)
   293  }
   294  
   295  func getVolumeSource(spec *volume.Spec) (*v1.NFSVolumeSource, bool, error) {
   296  	if spec.Volume != nil && spec.Volume.NFS != nil {
   297  		return spec.Volume.NFS, spec.Volume.NFS.ReadOnly, nil
   298  	} else if spec.PersistentVolume != nil &&
   299  		spec.PersistentVolume.Spec.NFS != nil {
   300  		return spec.PersistentVolume.Spec.NFS, spec.ReadOnly, nil
   301  	}
   302  
   303  	return nil, false, fmt.Errorf("Spec does not reference a NFS volume type")
   304  }
   305  
   306  func getServerFromSource(source *v1.NFSVolumeSource) string {
   307  	if netutil.IsIPv6String(source.Server) {
   308  		return fmt.Sprintf("[%s]", source.Server)
   309  	}
   310  	return source.Server
   311  }