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 }