k8s.io/kubernetes@v1.29.3/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) SupportsBulkVolumeVerification() bool { 105 return false 106 } 107 108 func (plugin *nfsPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 109 return false, nil 110 } 111 112 func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 113 return []v1.PersistentVolumeAccessMode{ 114 v1.ReadWriteOnce, 115 v1.ReadOnlyMany, 116 v1.ReadWriteMany, 117 } 118 } 119 120 func (plugin *nfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { 121 return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName())) 122 } 123 124 func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) { 125 source, readOnly, err := getVolumeSource(spec) 126 if err != nil { 127 return nil, err 128 } 129 return &nfsMounter{ 130 nfs: &nfs{ 131 volName: spec.Name(), 132 mounter: mounter, 133 pod: pod, 134 plugin: plugin, 135 MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)), 136 }, 137 server: getServerFromSource(source), 138 exportPath: source.Path, 139 readOnly: readOnly, 140 mountOptions: util.MountOptionFromSpec(spec), 141 }, nil 142 } 143 144 func (plugin *nfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 145 return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName())) 146 } 147 148 func (plugin *nfsPlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) { 149 return &nfsUnmounter{&nfs{ 150 volName: volName, 151 mounter: mounter, 152 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}}, 153 plugin: plugin, 154 MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)), 155 }}, nil 156 } 157 158 // Recycle recycles/scrubs clean an NFS volume. 159 // Recycle blocks until the pod has completed or any error occurs. 160 func (plugin *nfsPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error { 161 if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.NFS == nil { 162 return fmt.Errorf("spec.PersistentVolumeSource.NFS is nil") 163 } 164 165 pod := plugin.config.RecyclerPodTemplate 166 timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume) 167 // overrides 168 pod.Spec.ActiveDeadlineSeconds = &timeout 169 pod.GenerateName = "pv-recycler-nfs-" 170 pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{ 171 NFS: &v1.NFSVolumeSource{ 172 Server: spec.PersistentVolume.Spec.NFS.Server, 173 Path: spec.PersistentVolume.Spec.NFS.Path, 174 }, 175 } 176 return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder) 177 } 178 179 func (plugin *nfsPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 180 nfsVolume := &v1.Volume{ 181 Name: volumeName, 182 VolumeSource: v1.VolumeSource{ 183 NFS: &v1.NFSVolumeSource{ 184 Path: volumeName, 185 }, 186 }, 187 } 188 return volume.ReconstructedVolume{ 189 Spec: volume.NewSpecFromVolume(nfsVolume), 190 }, nil 191 } 192 193 // NFS volumes represent a bare host file or directory mount of an NFS export. 194 type nfs struct { 195 volName string 196 pod *v1.Pod 197 mounter mount.Interface 198 plugin *nfsPlugin 199 volume.MetricsProvider 200 } 201 202 func (nfsVolume *nfs) GetPath() string { 203 name := nfsPluginName 204 return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, utilstrings.EscapeQualifiedName(name), nfsVolume.volName) 205 } 206 207 type nfsMounter struct { 208 *nfs 209 server string 210 exportPath string 211 readOnly bool 212 mountOptions []string 213 } 214 215 var _ volume.Mounter = &nfsMounter{} 216 217 func (nfsMounter *nfsMounter) GetAttributes() volume.Attributes { 218 return volume.Attributes{ 219 ReadOnly: nfsMounter.readOnly, 220 Managed: false, 221 SELinuxRelabel: false, 222 } 223 } 224 225 // SetUp attaches the disk and bind mounts to the volume path. 226 func (nfsMounter *nfsMounter) SetUp(mounterArgs volume.MounterArgs) error { 227 return nfsMounter.SetUpAt(nfsMounter.GetPath(), mounterArgs) 228 } 229 230 func (nfsMounter *nfsMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 231 notMnt, err := mount.IsNotMountPoint(nfsMounter.mounter, dir) 232 klog.V(4).Infof("NFS mount set up: %s %v %v", dir, !notMnt, err) 233 if err != nil && !os.IsNotExist(err) { 234 return err 235 } 236 if !notMnt { 237 return nil 238 } 239 if err := os.MkdirAll(dir, 0750); err != nil { 240 return err 241 } 242 source := fmt.Sprintf("%s:%s", nfsMounter.server, nfsMounter.exportPath) 243 options := []string{} 244 if nfsMounter.readOnly { 245 options = append(options, "ro") 246 } 247 mountOptions := util.JoinMountOptions(nfsMounter.mountOptions, options) 248 err = nfsMounter.mounter.MountSensitiveWithoutSystemd(source, dir, "nfs", mountOptions, nil) 249 if err != nil { 250 notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir) 251 if mntErr != nil { 252 klog.Errorf("IsNotMountPoint check failed: %v", mntErr) 253 return err 254 } 255 if !notMnt { 256 if mntErr = nfsMounter.mounter.Unmount(dir); mntErr != nil { 257 klog.Errorf("Failed to unmount: %v", mntErr) 258 return err 259 } 260 notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir) 261 if mntErr != nil { 262 klog.Errorf("IsNotMountPoint check failed: %v", mntErr) 263 return err 264 } 265 if !notMnt { 266 // This is very odd, we don't expect it. We'll try again next sync loop. 267 klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir) 268 return err 269 } 270 } 271 os.Remove(dir) 272 return err 273 } 274 return nil 275 } 276 277 var _ volume.Unmounter = &nfsUnmounter{} 278 279 type nfsUnmounter struct { 280 *nfs 281 } 282 283 func (c *nfsUnmounter) TearDown() error { 284 return c.TearDownAt(c.GetPath()) 285 } 286 287 func (c *nfsUnmounter) TearDownAt(dir string) error { 288 // Use extensiveMountPointCheck to consult /proc/mounts. We can't use faster 289 // IsLikelyNotMountPoint (lstat()), since there may be root_squash on the 290 // NFS server and kubelet may not be able to do lstat/stat() there. 291 forceUnmounter, ok := c.mounter.(mount.MounterForceUnmounter) 292 if ok { 293 klog.V(4).Infof("Using force unmounter interface") 294 return mount.CleanupMountWithForce(dir, forceUnmounter, true /* extensiveMountPointCheck */, unMountTimeout) 295 } 296 return mount.CleanupMountPoint(dir, c.mounter, true /* extensiveMountPointCheck */) 297 } 298 299 func getVolumeSource(spec *volume.Spec) (*v1.NFSVolumeSource, bool, error) { 300 if spec.Volume != nil && spec.Volume.NFS != nil { 301 return spec.Volume.NFS, spec.Volume.NFS.ReadOnly, nil 302 } else if spec.PersistentVolume != nil && 303 spec.PersistentVolume.Spec.NFS != nil { 304 return spec.PersistentVolume.Spec.NFS, spec.ReadOnly, nil 305 } 306 307 return nil, false, fmt.Errorf("Spec does not reference a NFS volume type") 308 } 309 310 func getServerFromSource(source *v1.NFSVolumeSource) string { 311 if netutil.IsIPv6String(source.Server) { 312 return fmt.Sprintf("[%s]", source.Server) 313 } 314 return source.Server 315 }