diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 139a8eacbb3..5a15fb1f849 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -372,6 +372,7 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err mounter := mount.New(s.ExperimentalMounterPath) subpather := subpath.New(mounter) + hu := mount.NewHostUtil() var pluginRunner = exec.New() if s.Containerized { klog.V(2).Info("Running kubelet in containerized mode") @@ -382,6 +383,7 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err mounter = nsutil.NewMounter(s.RootDirectory, ne) // NSenter only valid on Linux subpather = subpath.NewNSEnter(mounter, ne, s.RootDirectory) + hu = nsutil.NewHostUtil(ne, s.RootDirectory) // an exec interface which can use nsenter for flex plugin calls pluginRunner, err = nsenter.NewNsenter(nsenter.DefaultHostRootFsPath, exec.New()) if err != nil { @@ -407,6 +409,7 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err KubeClient: nil, HeartbeatClient: nil, EventClient: nil, + HostUtil: hu, Mounter: mounter, Subpather: subpather, OOMAdjuster: oom.NewOOMAdjuster(), diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index d3e61681b76..7d6f6cfdc6d 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -250,6 +250,7 @@ type Dependencies struct { OnHeartbeatFailure func() KubeClient clientset.Interface Mounter mount.Interface + HostUtil mount.HostUtils OOMAdjuster *oom.OOMAdjuster OSInterface kubecontainer.OSInterface PodConfig *config.PodConfig @@ -518,6 +519,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, cgroupsPerQOS: kubeCfg.CgroupsPerQOS, cgroupRoot: kubeCfg.CgroupRoot, mounter: kubeDeps.Mounter, + hostutil: kubeDeps.HostUtil, subpather: kubeDeps.Subpather, maxPods: int(kubeCfg.MaxPods), podsPerCore: int(kubeCfg.PodsPerCore), @@ -812,6 +814,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, klet.volumePluginMgr, klet.containerRuntime, kubeDeps.Mounter, + kubeDeps.HostUtil, klet.getPodsDir(), kubeDeps.Recorder, experimentalCheckNodeCapabilitiesBeforeMount, @@ -1096,6 +1099,9 @@ type Kubelet struct { // Mounter to use for volumes. mounter mount.Interface + // hostutil to interact with filesystems + hostutil mount.HostUtils + // subpather to execute subpath actions subpather subpath.Interface @@ -1229,7 +1235,7 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getRootDir(), 0750); err != nil { return fmt.Errorf("error creating root directory: %v", err) } - if err := kl.mounter.MakeRShared(kl.getRootDir()); err != nil { + if err := kl.hostutil.MakeRShared(kl.getRootDir()); err != nil { return fmt.Errorf("error configuring root directory: %v", err) } if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index d5d65e4f1a8..e01c100b96e 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -128,7 +128,7 @@ func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVol } // makeMounts determines the mount points for the given container. -func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap, mounter mountutil.Interface, subpather subpath.Interface, expandEnvs []kubecontainer.EnvVar) ([]kubecontainer.Mount, func(), error) { +func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap, hu mountutil.HostUtils, subpather subpath.Interface, expandEnvs []kubecontainer.EnvVar) ([]kubecontainer.Mount, func(), error) { // Kubernetes only mounts on /etc/hosts if: // - container is not an infrastructure (pause) container // - container is not already mounting on /etc/hosts @@ -195,7 +195,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h volumePath := hostPath hostPath = filepath.Join(volumePath, subPath) - if subPathExists, err := mounter.ExistsPath(hostPath); err != nil { + if subPathExists, err := hu.ExistsPath(hostPath); err != nil { klog.Errorf("Could not determine if subPath %s exists; will not attempt to change its permissions", hostPath) } else if !subPathExists { // Create the sub path now because if it's auto-created later when referenced, it may have an @@ -203,7 +203,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h // when the pod specifies an fsGroup, and if the directory is not created here, Docker will // later auto-create it with the incorrect mode 0750 // Make extra care not to escape the volume! - perm, err := mounter.GetMode(volumePath) + perm, err := hu.GetMode(volumePath) if err != nil { return nil, cleanupAction, err } @@ -465,7 +465,7 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Contai } opts.Envs = append(opts.Envs, envs...) - mounts, cleanupAction, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes, kl.mounter, kl.subpather, opts.Envs) + mounts, cleanupAction, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes, kl.hostutil, kl.subpather, opts.Envs) if err != nil { return nil, cleanupAction, err } diff --git a/pkg/kubelet/kubelet_pods_linux_test.go b/pkg/kubelet/kubelet_pods_linux_test.go index 9ec6cdc3388..c4fe1148e39 100644 --- a/pkg/kubelet/kubelet_pods_linux_test.go +++ b/pkg/kubelet/kubelet_pods_linux_test.go @@ -241,7 +241,7 @@ func TestMakeMounts(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - fm := &mount.FakeMounter{} + fhu := &mount.FakeHostUtil{} fsp := &subpath.FakeSubpath{} pod := v1.Pod{ Spec: v1.PodSpec{ @@ -249,7 +249,7 @@ func TestMakeMounts(t *testing.T) { }, } - mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", "", tc.podVolumes, fm, fsp, nil) + mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", "", tc.podVolumes, fhu, fsp, nil) // validate only the error if we expect an error if tc.expectErr { diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index 03f528abb82..f6f1a8a0e53 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -53,7 +53,7 @@ import ( ) func TestDisabledSubpath(t *testing.T) { - fm := &mount.FakeMounter{} + fhu := &mount.FakeHostUtil{} fsp := &subpath.FakeSubpath{} pod := v1.Pod{ Spec: v1.PodSpec{ @@ -97,7 +97,7 @@ func TestDisabledSubpath(t *testing.T) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, false)() for name, test := range cases { - _, _, err := makeMounts(&pod, "/pod", &test.container, "fakepodname", "", "", podVolumes, fm, fsp, nil) + _, _, err := makeMounts(&pod, "/pod", &test.container, "fakepodname", "", "", podVolumes, fhu, fsp, nil) if err != nil && !test.expectError { t.Errorf("test %v failed: %v", name, err) } diff --git a/pkg/kubelet/kubelet_pods_windows_test.go b/pkg/kubelet/kubelet_pods_windows_test.go index 9e16cd118f3..16d514ee797 100644 --- a/pkg/kubelet/kubelet_pods_windows_test.go +++ b/pkg/kubelet/kubelet_pods_windows_test.go @@ -82,9 +82,9 @@ func TestMakeMountsWindows(t *testing.T) { }, } - fm := &mount.FakeMounter{} + fhu := &mount.FakeHostUtil{} fsp := &subpath.FakeSubpath{} - mounts, _, _ := makeMounts(&pod, "/pod", &container, "fakepodname", "", "", podVolumes, fm, fsp, nil) + mounts, _, _ := makeMounts(&pod, "/pod", &container, "fakepodname", "", "", podVolumes, fhu, fsp, nil) expectedMounts := []kubecontainer.Mount{ { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index a4c32c92740..d85896792e6 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -162,6 +162,7 @@ func newTestKubeletWithImageList( kubelet.heartbeatClient = fakeKubeClient kubelet.os = &containertest.FakeOS{} kubelet.mounter = &mount.FakeMounter{} + kubelet.hostutil = &mount.FakeHostUtil{} kubelet.subpather = &subpath.FakeSubpath{} kubelet.hostname = testKubeletHostname @@ -312,7 +313,6 @@ func newTestKubeletWithImageList( NewInitializedVolumePluginMgr(kubelet, kubelet.secretManager, kubelet.configMapManager, token.NewManager(kubelet.kubeClient), allPlugins, prober) require.NoError(t, err, "Failed to initialize VolumePluginMgr") - kubelet.mounter = &mount.FakeMounter{} kubelet.volumeManager = kubeletvolume.NewVolumeManager( controllerAttachDetachEnabled, kubelet.nodeName, @@ -322,6 +322,7 @@ func newTestKubeletWithImageList( kubelet.volumePluginMgr, fakeRuntime, kubelet.mounter, + kubelet.hostutil, kubelet.getPodsDir(), kubelet.recorder, false, /* experimentalCheckNodeCapabilitiesBeforeMount*/ diff --git a/pkg/kubelet/runonce_test.go b/pkg/kubelet/runonce_test.go index 7bd35077abd..03c58f07d06 100644 --- a/pkg/kubelet/runonce_test.go +++ b/pkg/kubelet/runonce_test.go @@ -85,6 +85,7 @@ func TestRunOnce(t *testing.T) { hostname: testKubeletHostname, nodeName: testKubeletHostname, runtimeState: newRuntimeState(time.Second), + hostutil: &mount.FakeHostUtil{}, } kb.containerManager = cm.NewStubContainerManager() @@ -103,6 +104,7 @@ func TestRunOnce(t *testing.T) { kb.volumePluginMgr, fakeRuntime, kb.mounter, + kb.hostutil, kb.getPodsDir(), kb.recorder, false, /* experimentalCheckNodeCapabilitiesBeforeMount */ diff --git a/pkg/kubelet/volume_host.go b/pkg/kubelet/volume_host.go index 6cf1a56e549..99d4d923425 100644 --- a/pkg/kubelet/volume_host.go +++ b/pkg/kubelet/volume_host.go @@ -160,6 +160,10 @@ func (kvh *kubeletVolumeHost) GetSubpather() subpath.Interface { return kvh.kubelet.subpather } +func (kvh *kubeletVolumeHost) GetHostUtil() mount.HostUtils { + return kvh.kubelet.hostutil +} + func (kvh *kubeletVolumeHost) GetInformerFactory() informers.SharedInformerFactory { return kvh.informerFactory } diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler.go b/pkg/kubelet/volumemanager/reconciler/reconciler.go index 3c77d20e84e..6a49bd36f7e 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler.go @@ -87,6 +87,7 @@ type Reconciler interface { // safely (prevents more than one operation from being triggered on the same // volume) // mounter - mounter passed in from kubelet, passed down unmount path +// hostutil - hostutil passed in from kubelet // volumePluginMgr - volume plugin manager passed from kubelet func NewReconciler( kubeClient clientset.Interface, @@ -99,6 +100,7 @@ func NewReconciler( populatorHasAddedPods func() bool, operationExecutor operationexecutor.OperationExecutor, mounter mount.Interface, + hostutil mount.HostUtils, volumePluginMgr *volumepkg.VolumePluginMgr, kubeletPodsDir string) Reconciler { return &reconciler{ @@ -112,6 +114,7 @@ func NewReconciler( populatorHasAddedPods: populatorHasAddedPods, operationExecutor: operationExecutor, mounter: mounter, + hostutil: hostutil, volumePluginMgr: volumePluginMgr, kubeletPodsDir: kubeletPodsDir, timeOfLastSync: time.Time{}, @@ -130,6 +133,7 @@ type reconciler struct { populatorHasAddedPods func() bool operationExecutor operationexecutor.OperationExecutor mounter mount.Interface + hostutil mount.HostUtils volumePluginMgr *volumepkg.VolumePluginMgr kubeletPodsDir string timeOfLastSync time.Time @@ -278,7 +282,7 @@ func (rc *reconciler) reconcile() { // Volume is globally mounted to device, unmount it klog.V(5).Infof(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountDevice", "")) err := rc.operationExecutor.UnmountDevice( - attachedVolume.AttachedVolume, rc.actualStateOfWorld, rc.mounter) + attachedVolume.AttachedVolume, rc.actualStateOfWorld, rc.hostutil) if err != nil && !nestedpendingoperations.IsAlreadyExists(err) && !exponentialbackoff.IsExponentialBackoff(err) { diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go index e7c4d5423cc..c8327ac4b21 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go @@ -84,6 +84,7 @@ func Test_Run_Positive_DoNothing(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) @@ -127,6 +128,7 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -204,6 +206,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -282,6 +285,7 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -371,6 +375,7 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -461,6 +466,7 @@ func Test_Run_Positive_VolumeAttachAndMap(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -545,6 +551,7 @@ func Test_Run_Positive_BlockVolumeMapControllerAttachEnabled(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -630,6 +637,7 @@ func Test_Run_Positive_BlockVolumeAttachMapUnmapDetach(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -725,6 +733,7 @@ func Test_Run_Positive_VolumeUnmapControllerAttachEnabled(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ @@ -918,11 +927,11 @@ func Test_GenerateUnmapDeviceFunc_Plugin_Not_Found(t *testing.T) { nil, /* fakeRecorder */ false, /* checkNodeCapabilitiesBeforeMount */ nil)) - var mounter mount.Interface + var hostutil mount.HostUtils volumeMode := v1.PersistentVolumeBlock tmpSpec := &volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{VolumeMode: &volumeMode}}} deviceToDetach := operationexecutor.AttachedVolume{VolumeSpec: tmpSpec, PluginName: "fake-file-plugin"} - err := oex.UnmountDevice(deviceToDetach, asw, mounter) + err := oex.UnmountDevice(deviceToDetach, asw, hostutil) // Assert if assert.Error(t, err) { assert.Contains(t, err.Error(), tc.expectedErrMsg) @@ -1004,6 +1013,7 @@ func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) @@ -1181,6 +1191,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) { hasAddedPods, oex, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, volumePluginMgr, kubeletPodsDir) pod := &v1.Pod{ diff --git a/pkg/kubelet/volumemanager/volume_manager.go b/pkg/kubelet/volumemanager/volume_manager.go index d2733e67dd4..2464bd0e473 100644 --- a/pkg/kubelet/volumemanager/volume_manager.go +++ b/pkg/kubelet/volumemanager/volume_manager.go @@ -151,6 +151,7 @@ func NewVolumeManager( volumePluginMgr *volume.VolumePluginMgr, kubeContainerRuntime container.Runtime, mounter mount.Interface, + hostutil mount.HostUtils, kubeletPodsDir string, recorder record.EventRecorder, checkNodeCapabilitiesBeforeMount bool, @@ -190,6 +191,7 @@ func NewVolumeManager( vm.desiredStateOfWorldPopulator.HasAddedPods, vm.operationExecutor, mounter, + hostutil, volumePluginMgr, kubeletPodsDir) diff --git a/pkg/kubelet/volumemanager/volume_manager_test.go b/pkg/kubelet/volumemanager/volume_manager_test.go index 4d03a097ac3..23ea0b5bfa0 100644 --- a/pkg/kubelet/volumemanager/volume_manager_test.go +++ b/pkg/kubelet/volumemanager/volume_manager_test.go @@ -232,6 +232,7 @@ func newTestVolumeManager(tmpDir string, podManager kubepod.Manager, kubeClient plugMgr, &containertest.FakeRuntime{}, &mount.FakeMounter{}, + &mount.FakeHostUtil{}, "", fakeRecorder, false, /* experimentalCheckNodeCapabilitiesBeforeMount */ diff --git a/pkg/kubemark/hollow_kubelet.go b/pkg/kubemark/hollow_kubelet.go index 0bdc6020e5f..44a9bd349fc 100644 --- a/pkg/kubemark/hollow_kubelet.go +++ b/pkg/kubemark/hollow_kubelet.go @@ -80,6 +80,7 @@ func NewHollowKubelet( OOMAdjuster: oom.NewFakeOOMAdjuster(), Mounter: mount.New("" /* default mount path */), Subpather: &subpath.FakeSubpath{}, + HostUtil: &mount.FakeHostUtil{}, } return &HollowKubelet{ diff --git a/pkg/util/mount/fake.go b/pkg/util/mount/fake.go index 22b46d95c93..e27d673c4ed 100644 --- a/pkg/util/mount/fake.go +++ b/pkg/util/mount/fake.go @@ -29,7 +29,6 @@ import ( type FakeMounter struct { MountPoints []MountPoint Log []FakeAction - Filesystem map[string]FileType // Error to return for a path when calling IsLikelyNotMountPoint MountCheckErrors map[string]error // Some tests run things in parallel, make sure the mounter does not produce @@ -166,56 +165,6 @@ func (f *FakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } -func (f *FakeMounter) DeviceOpened(pathname string) (bool, error) { - f.mutex.Lock() - defer f.mutex.Unlock() - - for _, mp := range f.MountPoints { - if mp.Device == pathname { - return true, nil - } - } - return false, nil -} - -func (f *FakeMounter) PathIsDevice(pathname string) (bool, error) { - return true, nil -} - -func (f *FakeMounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return getDeviceNameFromMount(f, mountPath, pluginMountDir) -} - -func (f *FakeMounter) MakeRShared(path string) error { - return nil -} - -func (f *FakeMounter) GetFileType(pathname string) (FileType, error) { - if t, ok := f.Filesystem[pathname]; ok { - return t, nil - } - return FileType("Directory"), nil -} - -func (f *FakeMounter) MakeDir(pathname string) error { - return nil -} - -func (f *FakeMounter) MakeFile(pathname string) error { - return nil -} - -func (f *FakeMounter) ExistsPath(pathname string) (bool, error) { - if _, ok := f.Filesystem[pathname]; ok { - return true, nil - } - return false, nil -} - -func (f *FakeMounter) EvalHostSymlinks(pathname string) (string, error) { - return pathname, nil -} - func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) { realpath, err := filepath.EvalSymlinks(pathname) if err != nil { @@ -225,14 +174,73 @@ func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) { return getMountRefsByDev(f, realpath) } -func (f *FakeMounter) GetFSGroup(pathname string) (int64, error) { +type FakeHostUtil struct { + MountPoints []MountPoint + Filesystem map[string]FileType + + mutex sync.Mutex +} + +var _ HostUtils = &FakeHostUtil{} + +func (hu *FakeHostUtil) DeviceOpened(pathname string) (bool, error) { + hu.mutex.Lock() + defer hu.mutex.Unlock() + + for _, mp := range hu.MountPoints { + if mp.Device == pathname { + return true, nil + } + } + return false, nil +} + +func (hu *FakeHostUtil) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (hu *FakeHostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + return getDeviceNameFromMount(mounter, mountPath, pluginMountDir) +} + +func (hu *FakeHostUtil) MakeRShared(path string) error { + return nil +} + +func (hu *FakeHostUtil) GetFileType(pathname string) (FileType, error) { + if t, ok := hu.Filesystem[pathname]; ok { + return t, nil + } + return FileType("Directory"), nil +} + +func (hu *FakeHostUtil) MakeDir(pathname string) error { + return nil +} + +func (hu *FakeHostUtil) MakeFile(pathname string) error { + return nil +} + +func (hu *FakeHostUtil) ExistsPath(pathname string) (bool, error) { + if _, ok := hu.Filesystem[pathname]; ok { + return true, nil + } + return false, nil +} + +func (hu *FakeHostUtil) EvalHostSymlinks(pathname string) (string, error) { + return pathname, nil +} + +func (hu *FakeHostUtil) GetFSGroup(pathname string) (int64, error) { return -1, errors.New("GetFSGroup not implemented") } -func (f *FakeMounter) GetSELinuxSupport(pathname string) (bool, error) { +func (hu *FakeHostUtil) GetSELinuxSupport(pathname string) (bool, error) { return false, errors.New("GetSELinuxSupport not implemented") } -func (f *FakeMounter) GetMode(pathname string) (os.FileMode, error) { +func (hu *FakeHostUtil) GetMode(pathname string) (os.FileMode, error) { return 0, errors.New("not implemented") } diff --git a/pkg/util/mount/mount.go b/pkg/util/mount/mount.go index 96dc68c9a03..4fa8856285b 100644 --- a/pkg/util/mount/mount.go +++ b/pkg/util/mount/mount.go @@ -55,6 +55,13 @@ type Interface interface { // IsLikelyNotMountPoint does NOT properly detect all mountpoint types // most notably linux bind mounts and symbolic link. IsLikelyNotMountPoint(file string) (bool, error) + // GetMountRefs finds all mount references to the path, returns a + // list of paths. Path could be a mountpoint path, device or a normal + // directory (for bind mount). + GetMountRefs(pathname string) ([]string, error) +} + +type HostUtils interface { // DeviceOpened determines if the device is in use elsewhere // on the system, i.e. still mounted. DeviceOpened(pathname string) (bool, error) @@ -62,7 +69,7 @@ type Interface interface { PathIsDevice(pathname string) (bool, error) // GetDeviceNameFromMount finds the device name by checking the mount path // to get the global mount path within its plugin directory - GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) + GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) // MakeRShared checks that given path is on a mount with 'rshared' mount // propagation. If not, it bind-mounts the path as rshared. MakeRShared(path string) error @@ -81,10 +88,6 @@ type Interface interface { // EvalHostSymlinks returns the path name after evaluating symlinks. // Will operate in the host mount namespace if kubelet is running in a container. EvalHostSymlinks(pathname string) (string, error) - // GetMountRefs finds all mount references to the path, returns a - // list of paths. Path could be a mountpoint path, device or a normal - // directory (for bind mount). - GetMountRefs(pathname string) ([]string, error) // GetFSGroup returns FSGroup of the path. GetFSGroup(pathname string) (int64, error) // GetSELinuxSupport returns true if given path is on a mount that supports @@ -123,6 +126,10 @@ type Exec interface { // the mount interface var _ Interface = &Mounter{} +// Compile-time check to ensure all HostUtil implementations satisfy +// the HostUtils Interface +var _ HostUtils = &hostUtil{} + // This represents a single line in /proc/mounts or /etc/fstab. type MountPoint struct { Device string @@ -239,7 +246,8 @@ func IsNotMountPoint(mounter Interface, file string) (bool, error) { } // Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts - resolvedFile, err := mounter.EvalHostSymlinks(file) + hu := NewHostUtil() + resolvedFile, err := hu.EvalHostSymlinks(file) if err != nil { return true, err } diff --git a/pkg/util/mount/mount_helper_test.go b/pkg/util/mount/mount_helper_test.go index 18a3e16c7ea..d09d03b45dd 100644 --- a/pkg/util/mount/mount_helper_test.go +++ b/pkg/util/mount/mount_helper_test.go @@ -21,11 +21,17 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "syscall" "testing" ) func TestDoCleanupMountPoint(t *testing.T) { + + if runtime.GOOS == "darwin" { + t.Skipf("not supported on GOOS=%s", runtime.GOOS) + } + const testMount = "test-mount" const defaultPerm = 0750 diff --git a/pkg/util/mount/mount_linux.go b/pkg/util/mount/mount_linux.go index 0f75d2184d5..0ad97e5539b 100644 --- a/pkg/util/mount/mount_linux.go +++ b/pkg/util/mount/mount_linux.go @@ -249,179 +249,21 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } -// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. -// If pathname is not a device, log and return false with nil error. -// If open returns errno EBUSY, return true with nil error. -// If open returns nil, return false with nil error. -// Otherwise, return false with error -func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { - return ExclusiveOpenFailsOnDevice(pathname) -} - -// PathIsDevice uses FileInfo returned from os.Stat to check if path refers -// to a device. -func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { - pathType, err := mounter.GetFileType(pathname) - isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev - return isDevice, err -} - -// ExclusiveOpenFailsOnDevice is shared with NsEnterMounter -func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) { - var isDevice bool - finfo, err := os.Stat(pathname) - if os.IsNotExist(err) { - isDevice = false +func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { + pathExists, pathErr := PathExists(pathname) + if !pathExists { + return []string{}, nil + } else if IsCorruptedMnt(pathErr) { + klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname) + return []string{}, nil + } else if pathErr != nil { + return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr) } - // err in call to os.Stat - if err != nil { - return false, fmt.Errorf( - "PathIsDevice failed for path %q: %v", - pathname, - err) - } - // path refers to a device - if finfo.Mode()&os.ModeDevice != 0 { - isDevice = true - } - - if !isDevice { - klog.Errorf("Path %q is not referring to a device.", pathname) - return false, nil - } - fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL, 0) - // If the device is in use, open will return an invalid fd. - // When this happens, it is expected that Close will fail and throw an error. - defer unix.Close(fd) - if errno == nil { - // device not in use - return false, nil - } else if errno == unix.EBUSY { - // device is in use - return true, nil - } - // error during call to Open - return false, errno -} - -//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point -func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir) -} - -func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { - return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir) -} - -// GetDeviceNameFromMountLinux find the device name from /proc/mounts in which -// the mount path reference should match the given plugin mount directory. In case no mount path reference -// matches, returns the volume name taken from its given mountPath -// This implementation is shared with NsEnterMounter -func GetDeviceNameFromMountLinux(mounter Interface, mountPath, pluginMountDir string) (string, error) { - refs, err := mounter.GetMountRefs(mountPath) - if err != nil { - klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) - return "", err - } - if len(refs) == 0 { - klog.V(4).Infof("Directory %s is not mounted", mountPath) - return "", fmt.Errorf("directory %s is not mounted", mountPath) - } - for _, ref := range refs { - if strings.HasPrefix(ref, pluginMountDir) { - volumeID, err := filepath.Rel(pluginMountDir, ref) - if err != nil { - klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) - return "", err - } - return volumeID, nil - } - } - - return path.Base(mountPath), nil -} - -// ListProcMounts is shared with NsEnterMounter -func ListProcMounts(mountFilePath string) ([]MountPoint, error) { - content, err := utilio.ConsistentRead(mountFilePath, maxListTries) + realpath, err := filepath.EvalSymlinks(pathname) if err != nil { return nil, err } - return parseProcMounts(content) -} - -func parseProcMounts(content []byte) ([]MountPoint, error) { - out := []MountPoint{} - lines := strings.Split(string(content), "\n") - for _, line := range lines { - if line == "" { - // the last split() item is empty string following the last \n - continue - } - fields := strings.Fields(line) - if len(fields) != expectedNumFieldsPerLine { - return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) - } - - mp := MountPoint{ - Device: fields[0], - Path: fields[1], - Type: fields[2], - Opts: strings.Split(fields[3], ","), - } - - freq, err := strconv.Atoi(fields[4]) - if err != nil { - return nil, err - } - mp.Freq = freq - - pass, err := strconv.Atoi(fields[5]) - if err != nil { - return nil, err - } - mp.Pass = pass - - out = append(out, mp) - } - return out, nil -} - -func (mounter *Mounter) MakeRShared(path string) error { - return DoMakeRShared(path, procMountInfoPath) -} - -func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { - return getFileType(pathname) -} - -func (mounter *Mounter) MakeDir(pathname string) error { - err := os.MkdirAll(pathname, os.FileMode(0755)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -func (mounter *Mounter) MakeFile(pathname string) error { - f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644)) - defer f.Close() - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -func (mounter *Mounter) ExistsPath(pathname string) (bool, error) { - return utilpath.Exists(utilpath.CheckFollowSymlink, pathname) -} - -func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) { - return filepath.EvalSymlinks(pathname) + return SearchMountPoints(realpath, procMountInfoPath) } // formatAndMount uses unix utils to format and mount the given disk @@ -562,6 +404,188 @@ func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) { return fstype, nil } +// ListProcMounts is shared with NsEnterMounter +func ListProcMounts(mountFilePath string) ([]MountPoint, error) { + content, err := utilio.ConsistentRead(mountFilePath, maxListTries) + if err != nil { + return nil, err + } + return parseProcMounts(content) +} + +func parseProcMounts(content []byte) ([]MountPoint, error) { + out := []MountPoint{} + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if line == "" { + // the last split() item is empty string following the last \n + continue + } + fields := strings.Fields(line) + if len(fields) != expectedNumFieldsPerLine { + return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) + } + + mp := MountPoint{ + Device: fields[0], + Path: fields[1], + Type: fields[2], + Opts: strings.Split(fields[3], ","), + } + + freq, err := strconv.Atoi(fields[4]) + if err != nil { + return nil, err + } + mp.Freq = freq + + pass, err := strconv.Atoi(fields[5]) + if err != nil { + return nil, err + } + mp.Pass = pass + + out = append(out, mp) + } + return out, nil +} + +type hostUtil struct { +} + +func NewHostUtil() HostUtils { + return &hostUtil{} +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// If pathname is not a device, log and return false with nil error. +// If open returns errno EBUSY, return true with nil error. +// If open returns nil, return false with nil error. +// Otherwise, return false with error +func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) { + return ExclusiveOpenFailsOnDevice(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) { + pathType, err := hu.GetFileType(pathname) + isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev + return isDevice, err +} + +// ExclusiveOpenFailsOnDevice is shared with NsEnterMounter +func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) { + var isDevice bool + finfo, err := os.Stat(pathname) + if os.IsNotExist(err) { + isDevice = false + } + // err in call to os.Stat + if err != nil { + return false, fmt.Errorf( + "PathIsDevice failed for path %q: %v", + pathname, + err) + } + // path refers to a device + if finfo.Mode()&os.ModeDevice != 0 { + isDevice = true + } + + if !isDevice { + klog.Errorf("Path %q is not referring to a device.", pathname) + return false, nil + } + fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL, 0) + // If the device is in use, open will return an invalid fd. + // When this happens, it is expected that Close will fail and throw an error. + defer unix.Close(fd) + if errno == nil { + // device not in use + return false, nil + } else if errno == unix.EBUSY { + // device is in use + return true, nil + } + // error during call to Open + return false, errno +} + +//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point +func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir) +} + +func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir) +} + +// GetDeviceNameFromMountLinux find the device name from /proc/mounts in which +// the mount path reference should match the given plugin mount directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +// This implementation is shared with NsEnterMounter +func GetDeviceNameFromMountLinux(mounter Interface, mountPath, pluginMountDir string) (string, error) { + refs, err := mounter.GetMountRefs(mountPath) + if err != nil { + klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + klog.V(4).Infof("Directory %s is not mounted", mountPath) + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + for _, ref := range refs { + if strings.HasPrefix(ref, pluginMountDir) { + volumeID, err := filepath.Rel(pluginMountDir, ref) + if err != nil { + klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + +func (hu *hostUtil) MakeRShared(path string) error { + return DoMakeRShared(path, procMountInfoPath) +} + +func (hu *hostUtil) GetFileType(pathname string) (FileType, error) { + return getFileType(pathname) +} + +func (hu *hostUtil) MakeDir(pathname string) error { + err := os.MkdirAll(pathname, os.FileMode(0755)) + if err != nil { + if !os.IsExist(err) { + return err + } + } + return nil +} + +func (hu *hostUtil) MakeFile(pathname string) error { + f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644)) + defer f.Close() + if err != nil { + if !os.IsExist(err) { + return err + } + } + return nil +} + +func (hu *hostUtil) ExistsPath(pathname string) (bool, error) { + return utilpath.Exists(utilpath.CheckFollowSymlink, pathname) +} + +func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) { + return filepath.EvalSymlinks(pathname) +} + // isShared returns true, if given path is on a mount point that has shared // mount propagation. func isShared(mount string, mountInfoPath string) (bool, error) { @@ -726,28 +750,11 @@ func GetSELinux(path string, mountInfoFilename string) (bool, error) { return false, nil } -func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { - pathExists, pathErr := PathExists(pathname) - if !pathExists { - return []string{}, nil - } else if IsCorruptedMnt(pathErr) { - klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname) - return []string{}, nil - } else if pathErr != nil { - return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr) - } - realpath, err := filepath.EvalSymlinks(pathname) - if err != nil { - return nil, err - } - return SearchMountPoints(realpath, procMountInfoPath) -} - -func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) { +func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) { return GetSELinux(pathname, procMountInfoPath) } -func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) { +func (hu *hostUtil) GetFSGroup(pathname string) (int64, error) { realpath, err := filepath.EvalSymlinks(pathname) if err != nil { return 0, err @@ -755,7 +762,7 @@ func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) { return GetFSGroupLinux(realpath) } -func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) { +func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) { return GetModeLinux(pathname) } diff --git a/pkg/util/mount/mount_linux_test.go b/pkg/util/mount/mount_linux_test.go index 6a53e266239..e8ff8251d8f 100644 --- a/pkg/util/mount/mount_linux_test.go +++ b/pkg/util/mount/mount_linux_test.go @@ -661,7 +661,7 @@ func createSocketFile(socketDir string) (string, error) { } func TestGetFileType(t *testing.T) { - mounter := Mounter{"fake/path", false} + hu := NewHostUtil() testCase := []struct { name string @@ -750,7 +750,7 @@ func TestGetFileType(t *testing.T) { defer os.RemoveAll(cleanUpPath) } - fileType, err := mounter.GetFileType(path) + fileType, err := hu.GetFileType(path) if err != nil { t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) } diff --git a/pkg/util/mount/mount_unsupported.go b/pkg/util/mount/mount_unsupported.go index 362ece518a3..e502b168ef1 100644 --- a/pkg/util/mount/mount_unsupported.go +++ b/pkg/util/mount/mount_unsupported.go @@ -58,24 +58,8 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, unsupportedErr } -func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return "", unsupportedErr -} - -func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { - return "", unsupportedErr -} - -func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { - return false, unsupportedErr -} - -func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { - return true, unsupportedErr -} - -func (mounter *Mounter) MakeRShared(path string) error { - return unsupportedErr +func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { + return nil, errors.New("not implemented") } func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { @@ -86,38 +70,65 @@ func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, erro return true, unsupportedErr } -func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { - return FileType("fake"), unsupportedErr -} - -func (mounter *Mounter) MakeDir(pathname string) error { - return unsupportedErr -} - -func (mounter *Mounter) MakeFile(pathname string) error { - return unsupportedErr -} - -func (mounter *Mounter) ExistsPath(pathname string) (bool, error) { - return true, errors.New("not implemented") -} - -func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) { +func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { return "", unsupportedErr } -func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { - return nil, errors.New("not implemented") +type hostUtil struct{} + +func NewHostUtil() HostUtils { + return &hostUtil{} } -func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) { - return -1, errors.New("not implemented") +// DeviceOpened determines if the device is in use elsewhere +func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) { + return false, unsupportedErr } -func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) { - return false, errors.New("not implemented") +// PathIsDevice determines if a path is a device. +func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) { + return true, unsupportedErr } -func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) { - return 0, errors.New("not implemented") +// GetDeviceNameFromMount finds the device name by checking the mount path +// to get the global mount path within its plugin directory +func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + return "", unsupportedErr +} + +func (hu *hostUtil) MakeRShared(path string) error { + return unsupportedErr +} + +func (hu *hostUtil) GetFileType(pathname string) (FileType, error) { + return FileType("fake"), unsupportedErr +} + +func (hu *hostUtil) MakeFile(pathname string) error { + return unsupportedErr +} + +func (hu *hostUtil) MakeDir(pathname string) error { + return unsupportedErr +} + +func (hu *hostUtil) ExistsPath(pathname string) (bool, error) { + return true, unsupportedErr +} + +// EvalHostSymlinks returns the path name after evaluating symlinks +func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) { + return "", unsupportedErr +} + +func (hu *hostUtil) GetFSGroup(pathname string) (int64, error) { + return -1, unsupportedErr +} + +func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) { + return false, unsupportedErr +} + +func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) { + return 0, unsupportedErr } diff --git a/pkg/util/mount/mount_windows.go b/pkg/util/mount/mount_windows.go index dd79842b999..b420fbf7030 100644 --- a/pkg/util/mount/mount_windows.go +++ b/pkg/util/mount/mount_windows.go @@ -185,7 +185,8 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { if err != nil { return true, fmt.Errorf("readlink error: %v", err) } - exists, err := mounter.ExistsPath(target) + hu := NewHostUtil() + exists, err := hu.ExistsPath(target) if err != nil { return true, err } @@ -195,90 +196,19 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } -// GetDeviceNameFromMount given a mnt point, find the device -func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return getDeviceNameFromMount(mounter, mountPath, pluginMountDir) -} - -// getDeviceNameFromMount find the device(drive) name in which -// the mount path reference should match the given plugin mount directory. In case no mount path reference -// matches, returns the volume name taken from its given mountPath -func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { - refs, err := mounter.GetMountRefs(mountPath) - if err != nil { - klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) - return "", err +// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows +func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { + windowsPath := normalizeWindowsPath(pathname) + pathExists, pathErr := PathExists(windowsPath) + if !pathExists { + return []string{}, nil + } else if IsCorruptedMnt(pathErr) { + klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", windowsPath) + return []string{}, nil + } else if pathErr != nil { + return nil, fmt.Errorf("error checking path %s: %v", windowsPath, pathErr) } - if len(refs) == 0 { - return "", fmt.Errorf("directory %s is not mounted", mountPath) - } - basemountPath := normalizeWindowsPath(pluginMountDir) - for _, ref := range refs { - if strings.Contains(ref, basemountPath) { - volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref) - if err != nil { - klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) - return "", err - } - return volumeID, nil - } - } - - return path.Base(mountPath), nil -} - -// DeviceOpened determines if the device is in use elsewhere -func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { - return false, nil -} - -// PathIsDevice determines if a path is a device. -func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { - return false, nil -} - -// MakeRShared checks that given path is on a mount with 'rshared' mount -// propagation. Empty implementation here. -func (mounter *Mounter) MakeRShared(path string) error { - return nil -} - -// GetFileType checks for sockets/block/character devices -func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { - return getFileType(pathname) -} - -// MakeFile creates a new directory -func (mounter *Mounter) MakeDir(pathname string) error { - err := os.MkdirAll(pathname, os.FileMode(0755)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -// MakeFile creates an empty file -func (mounter *Mounter) MakeFile(pathname string) error { - f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644)) - defer f.Close() - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -// ExistsPath checks whether the path exists -func (mounter *Mounter) ExistsPath(pathname string) (bool, error) { - return utilpath.Exists(utilpath.CheckFollowSymlink, pathname) -} - -// EvalHostSymlinks returns the path name after evaluating symlinks -func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) { - return filepath.EvalSymlinks(pathname) + return []string{pathname}, nil } func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { @@ -379,33 +309,110 @@ func getAllParentLinks(path string) ([]string, error) { return links, nil } -// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows -func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { - windowsPath := normalizeWindowsPath(pathname) - pathExists, pathErr := PathExists(windowsPath) - if !pathExists { - return []string{}, nil - } else if IsCorruptedMnt(pathErr) { - klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", windowsPath) - return []string{}, nil - } else if pathErr != nil { - return nil, fmt.Errorf("error checking path %s: %v", windowsPath, pathErr) +type hostUtil struct{} + +func NewHostUtil() HostUtils { + return &hostUtil{} +} + +// GetDeviceNameFromMount given a mnt point, find the device +func (hu *hostUtil) GetDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + return getDeviceNameFromMount(mounter, mountPath, pluginMountDir) +} + +// getDeviceNameFromMount find the device(drive) name in which +// the mount path reference should match the given plugin mount directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) { + refs, err := mounter.GetMountRefs(mountPath) + if err != nil { + klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err } - return []string{pathname}, nil + if len(refs) == 0 { + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := normalizeWindowsPath(pluginMountDir) + for _, ref := range refs { + if strings.Contains(ref, basemountPath) { + volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref) + if err != nil { + klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + +// DeviceOpened determines if the device is in use elsewhere +func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +// PathIsDevice determines if a path is a device. +func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) { + return false, nil +} + +// MakeRShared checks that given path is on a mount with 'rshared' mount +// propagation. Empty implementation here. +func (hu *hostUtil) MakeRShared(path string) error { + return nil +} + +// GetFileType checks for sockets/block/character devices +func (hu *(hostUtil)) GetFileType(pathname string) (FileType, error) { + return getFileType(pathname) +} + +// MakeFile creates a new directory +func (hu *hostUtil) MakeDir(pathname string) error { + err := os.MkdirAll(pathname, os.FileMode(0755)) + if err != nil { + if !os.IsExist(err) { + return err + } + } + return nil +} + +// MakeFile creates an empty file +func (hu *hostUtil) MakeFile(pathname string) error { + f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644)) + defer f.Close() + if err != nil { + if !os.IsExist(err) { + return err + } + } + return nil +} + +// ExistsPath checks whether the path exists +func (hu *hostUtil) ExistsPath(pathname string) (bool, error) { + return utilpath.Exists(utilpath.CheckFollowSymlink, pathname) +} + +// EvalHostSymlinks returns the path name after evaluating symlinks +func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) { + return filepath.EvalSymlinks(pathname) } // Note that on windows, it always returns 0. We actually don't set FSGroup on // windows platform, see SetVolumeOwnership implementation. -func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) { +func (hu *hostUtil) GetFSGroup(pathname string) (int64, error) { return 0, nil } -func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) { +func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) { // Windows does not support SELinux. return false, nil } -func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) { +func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) { info, err := os.Stat(pathname) if err != nil { return 0, err diff --git a/pkg/util/mount/mount_windows_test.go b/pkg/util/mount/mount_windows_test.go index 3ec233e095d..9af846ddd9b 100644 --- a/pkg/util/mount/mount_windows_test.go +++ b/pkg/util/mount/mount_windows_test.go @@ -175,7 +175,7 @@ func TestPathWithinBase(t *testing.T) { } func TestGetFileType(t *testing.T) { - mounter := New("fake/path") + hu := NewHostUtil() testCase := []struct { name string @@ -213,7 +213,7 @@ func TestGetFileType(t *testing.T) { defer os.RemoveAll(cleanUpPath) } - fileType, err := mounter.GetFileType(path) + fileType, err := hu.GetFileType(path) if err != nil { t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) } diff --git a/pkg/volume/awsebs/aws_ebs.go b/pkg/volume/awsebs/aws_ebs.go index fb0b3ebf6c7..d0979896e2e 100644 --- a/pkg/volume/awsebs/aws_ebs.go +++ b/pkg/volume/awsebs/aws_ebs.go @@ -250,8 +250,13 @@ func getVolumeSource( func (plugin *awsElasticBlockStorePlugin) ConstructVolumeSpec(volName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - volumeID, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + volumeID, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err } diff --git a/pkg/volume/azure_dd/azure_dd.go b/pkg/volume/azure_dd/azure_dd.go index 0f5fe506cac..2eece44dd69 100644 --- a/pkg/volume/azure_dd/azure_dd.go +++ b/pkg/volume/azure_dd/azure_dd.go @@ -309,8 +309,13 @@ var _ volume.NodeExpandableVolumePlugin = &azureDataDiskPlugin{} func (plugin *azureDataDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err diff --git a/pkg/volume/cinder/cinder.go b/pkg/volume/cinder/cinder.go index 9f1d8492be8..849faa82b51 100644 --- a/pkg/volume/cinder/cinder.go +++ b/pkg/volume/cinder/cinder.go @@ -267,8 +267,13 @@ func (plugin *cinderPlugin) getCloudProvider() (BlockStorageProvider, error) { func (plugin *cinderPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err } diff --git a/pkg/volume/csi/csi_mounter.go b/pkg/volume/csi/csi_mounter.go index 1af3d0af5a3..44153791049 100644 --- a/pkg/volume/csi/csi_mounter.go +++ b/pkg/volume/csi/csi_mounter.go @@ -69,6 +69,7 @@ type csiMountMgr struct { podUID types.UID options volume.VolumeOptions publishContext map[string]string + kubeVolHost volume.KubeletVolumeHost volume.MetricsProvider } @@ -328,9 +329,9 @@ func (c *csiMountMgr) podAttributes() (map[string]string, error) { } func (c *csiMountMgr) GetAttributes() volume.Attributes { - mounter := c.plugin.host.GetMounter(c.plugin.GetPluginName()) path := c.GetPath() - supportSelinux, err := mounter.GetSELinuxSupport(path) + hu := c.kubeVolHost.GetHostUtil() + supportSelinux, err := hu.GetSELinuxSupport(path) if err != nil { klog.V(2).Info(log("error checking for SELinux support: %s", err)) // Best guess diff --git a/pkg/volume/csi/csi_plugin.go b/pkg/volume/csi/csi_plugin.go index 6abb7bcb75e..64387123633 100644 --- a/pkg/volume/csi/csi_plugin.go +++ b/pkg/volume/csi/csi_plugin.go @@ -391,6 +391,12 @@ func (p *csiPlugin) NewMounter( return nil, errors.New("failed to get a Kubernetes client") } + kvh, ok := p.host.(volume.KubeletVolumeHost) + if !ok { + klog.Error(log("cast from VolumeHost to KubeletVolumeHost failed")) + return nil, errors.New("cast from VolumeHost to KubeletVolumeHost failed") + } + mounter := &csiMountMgr{ plugin: p, k8s: k8s, @@ -402,6 +408,7 @@ func (p *csiPlugin) NewMounter( volumeID: volumeHandle, specVolumeID: spec.Name(), readOnly: readOnly, + kubeVolHost: kvh, } mounter.csiClientGetter.driverName = csiDriverName(driverName) @@ -447,10 +454,17 @@ func (p *csiPlugin) NewMounter( func (p *csiPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) { klog.V(4).Infof(log("setting up unmounter for [name=%v, podUID=%v]", specName, podUID)) + kvh, ok := p.host.(volume.KubeletVolumeHost) + if !ok { + klog.Error(log("cast from VolumeHost to KubeletVolumeHost failed")) + return nil, errors.New("cast from VolumeHost to KubeletVolumeHost failed") + } + unmounter := &csiMountMgr{ plugin: p, podUID: podUID, specVolumeID: specName, + kubeVolHost: kvh, } // load volume info from file diff --git a/pkg/volume/gcepd/gce_pd.go b/pkg/volume/gcepd/gce_pd.go index 837266e8533..969a2ccbf46 100644 --- a/pkg/volume/gcepd/gce_pd.go +++ b/pkg/volume/gcepd/gce_pd.go @@ -288,8 +288,13 @@ var _ volume.NodeExpandableVolumePlugin = &gcePersistentDiskPlugin{} func (plugin *gcePersistentDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err } diff --git a/pkg/volume/hostpath/host_path.go b/pkg/volume/hostpath/host_path.go index 0f4c46ff4bf..2868b90b704 100644 --- a/pkg/volume/hostpath/host_path.go +++ b/pkg/volume/hostpath/host_path.go @@ -118,10 +118,15 @@ func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts vo } else { pathType = hostPathVolumeSource.Type } + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } return &hostPathMounter{ hostPath: &hostPath{path: path, pathType: pathType}, readOnly: readOnly, mounter: plugin.host.GetMounter(plugin.GetPluginName()), + hu: kvh.GetHostUtil(), }, nil } @@ -202,6 +207,7 @@ type hostPathMounter struct { *hostPath readOnly bool mounter mount.Interface + hu mount.HostUtils } var _ volume.Mounter = &hostPathMounter{} @@ -231,7 +237,7 @@ func (b *hostPathMounter) SetUp(mounterArgs volume.MounterArgs) error { if *b.pathType == v1.HostPathUnset { return nil } - return checkType(b.GetPath(), b.pathType, b.mounter) + return checkType(b.GetPath(), b.pathType, b.hu) } // SetUpAt does not make sense for host paths - probably programmer error. @@ -352,13 +358,13 @@ type hostPathTypeChecker interface { } type fileTypeChecker struct { - path string - exists bool - mounter mount.Interface + path string + exists bool + hu mount.HostUtils } func (ftc *fileTypeChecker) Exists() bool { - exists, err := ftc.mounter.ExistsPath(ftc.path) + exists, err := ftc.hu.ExistsPath(ftc.path) return exists && err == nil } @@ -370,14 +376,14 @@ func (ftc *fileTypeChecker) IsFile() bool { } func (ftc *fileTypeChecker) MakeFile() error { - return ftc.mounter.MakeFile(ftc.path) + return ftc.hu.MakeFile(ftc.path) } func (ftc *fileTypeChecker) IsDir() bool { if !ftc.Exists() { return false } - pathType, err := ftc.mounter.GetFileType(ftc.path) + pathType, err := ftc.hu.GetFileType(ftc.path) if err != nil { return false } @@ -385,11 +391,11 @@ func (ftc *fileTypeChecker) IsDir() bool { } func (ftc *fileTypeChecker) MakeDir() error { - return ftc.mounter.MakeDir(ftc.path) + return ftc.hu.MakeDir(ftc.path) } func (ftc *fileTypeChecker) IsBlock() bool { - blkDevType, err := ftc.mounter.GetFileType(ftc.path) + blkDevType, err := ftc.hu.GetFileType(ftc.path) if err != nil { return false } @@ -397,7 +403,7 @@ func (ftc *fileTypeChecker) IsBlock() bool { } func (ftc *fileTypeChecker) IsChar() bool { - charDevType, err := ftc.mounter.GetFileType(ftc.path) + charDevType, err := ftc.hu.GetFileType(ftc.path) if err != nil { return false } @@ -405,7 +411,7 @@ func (ftc *fileTypeChecker) IsChar() bool { } func (ftc *fileTypeChecker) IsSocket() bool { - socketType, err := ftc.mounter.GetFileType(ftc.path) + socketType, err := ftc.hu.GetFileType(ftc.path) if err != nil { return false } @@ -416,13 +422,13 @@ func (ftc *fileTypeChecker) GetPath() string { return ftc.path } -func newFileTypeChecker(path string, mounter mount.Interface) hostPathTypeChecker { - return &fileTypeChecker{path: path, mounter: mounter} +func newFileTypeChecker(path string, hu mount.HostUtils) hostPathTypeChecker { + return &fileTypeChecker{path: path, hu: hu} } // checkType checks whether the given path is the exact pathType -func checkType(path string, pathType *v1.HostPathType, mounter mount.Interface) error { - return checkTypeInternal(newFileTypeChecker(path, mounter), pathType) +func checkType(path string, pathType *v1.HostPathType, hu mount.HostUtils) error { + return checkTypeInternal(newFileTypeChecker(path, hu), pathType) } func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error { diff --git a/pkg/volume/hostpath/host_path_test.go b/pkg/volume/hostpath/host_path_test.go index 23da5d3d6af..e4ebee7592c 100644 --- a/pkg/volume/hostpath/host_path_test.go +++ b/pkg/volume/hostpath/host_path_test.go @@ -386,7 +386,7 @@ func TestOSFileTypeChecker(t *testing.T) { } for i, tc := range testCases { - fakeFTC := &utilmount.FakeMounter{ + fakeFTC := &utilmount.FakeHostUtil{ Filesystem: map[string]utilmount.FileType{ tc.path: utilmount.FileType(tc.desiredType), }, diff --git a/pkg/volume/local/local.go b/pkg/volume/local/local.go index 4457f9aadb9..4e42c27c117 100644 --- a/pkg/volume/local/local.go +++ b/pkg/volume/local/local.go @@ -124,12 +124,18 @@ func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ vo return nil, err } + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + return &localVolumeMounter{ localVolume: &localVolume{ pod: pod, podUID: pod.UID, volName: spec.Name(), mounter: plugin.host.GetMounter(plugin.GetPluginName()), + hostUtil: kvh.GetHostUtil(), plugin: plugin, globalPath: globalLocalPath, MetricsProvider: volume.NewMetricsStatFS(plugin.host.GetPodVolumeDir(pod.UID, utilstrings.EscapeQualifiedName(localVolumePluginName), spec.Name())), @@ -230,7 +236,12 @@ func (plugin *localVolumePlugin) getGlobalLocalPath(spec *volume.Spec) (string, return "", fmt.Errorf("local volume source is nil or local path is not set") } - fileType, err := plugin.host.GetMounter(plugin.GetPluginName()).GetFileType(spec.PersistentVolume.Spec.Local.Path) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return "", fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + + fileType, err := kvh.GetHostUtil().GetFileType(spec.PersistentVolume.Spec.Local.Path) if err != nil { return "", err } @@ -247,8 +258,9 @@ func (plugin *localVolumePlugin) getGlobalLocalPath(spec *volume.Spec) (string, var _ volume.DeviceMountableVolumePlugin = &localVolumePlugin{} type deviceMounter struct { - plugin *localVolumePlugin - mounter *mount.SafeFormatAndMount + plugin *localVolumePlugin + mounter *mount.SafeFormatAndMount + hostUtil mount.HostUtils } var _ volume.DeviceMounter = &deviceMounter{} @@ -258,9 +270,14 @@ func (plugin *localVolumePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) } func (plugin *localVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) { + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } return &deviceMounter{ - plugin: plugin, - mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + plugin: plugin, + mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host), + hostUtil: kvh.GetHostUtil(), }, nil } @@ -307,7 +324,7 @@ func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, devic if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 { return fmt.Errorf("local volume source is nil or local path is not set") } - fileType, err := dm.mounter.GetFileType(spec.PersistentVolume.Spec.Local.Path) + fileType, err := dm.hostUtil.GetFileType(spec.PersistentVolume.Spec.Local.Path) if err != nil { return err } @@ -390,8 +407,9 @@ type localVolume struct { // Global path to the volume globalPath string // Mounter interface that provides system calls to mount the global path to the pod local path. - mounter mount.Interface - plugin *localVolumePlugin + mounter mount.Interface + hostUtil mount.HostUtils + plugin *localVolumePlugin volume.MetricsProvider } @@ -462,7 +480,7 @@ func (m *localVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) refs = m.filterPodMounts(refs) if len(refs) > 0 { fsGroupNew := int64(*mounterArgs.FsGroup) - fsGroupOld, err := m.mounter.GetFSGroup(m.globalPath) + fsGroupOld, err := m.hostUtil.GetFSGroup(m.globalPath) if err != nil { return fmt.Errorf("failed to check fsGroup for %s (%v)", m.globalPath, err) } diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index e41fc4e52c3..933a18fc7f8 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -334,6 +334,8 @@ type KubeletVolumeHost interface { CSIDriversSynced() cache.InformerSynced // WaitForCacheSync is a helper function that waits for cache sync for CSIDriverLister WaitForCacheSync() error + // Returns HostUtils Interface + GetHostUtil() mount.HostUtils } // AttachDetachVolumeHost is a AttachDetach Controller specific interface that plugins can use diff --git a/pkg/volume/rbd/rbd.go b/pkg/volume/rbd/rbd.go index da07ba64765..a0ee94949bf 100644 --- a/pkg/volume/rbd/rbd.go +++ b/pkg/volume/rbd/rbd.go @@ -374,8 +374,13 @@ func (plugin *rbdPlugin) newUnmounterInternal(volName string, podUID types.UID, func (plugin *rbdPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := volutil.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err } diff --git a/pkg/volume/scaleio/sio_volume.go b/pkg/volume/scaleio/sio_volume.go index 4b7fd8ebb23..67bc549b9b9 100644 --- a/pkg/volume/scaleio/sio_volume.go +++ b/pkg/volume/scaleio/sio_volume.go @@ -201,7 +201,12 @@ func (v *sioVolume) TearDownAt(dir string) error { klog.V(4).Info(log("dir %s unmounted successfully", dir)) // detach/unmap - deviceBusy, err := mounter.DeviceOpened(dev) + kvh, ok := v.plugin.host.(volume.KubeletVolumeHost) + if !ok { + return fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() + deviceBusy, err := hu.DeviceOpened(dev) if err != nil { klog.Error(log("teardown unable to get status for device %s: %v", dev, err)) return err diff --git a/pkg/volume/testing/BUILD b/pkg/volume/testing/BUILD index 0994e3df216..94a957466f1 100644 --- a/pkg/volume/testing/BUILD +++ b/pkg/volume/testing/BUILD @@ -25,8 +25,10 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library", + "//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/listers/storage/v1beta1:go_default_library", + "//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/client-go/tools/record:go_default_library", "//staging/src/k8s.io/client-go/util/testing:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library", diff --git a/pkg/volume/testing/testing.go b/pkg/volume/testing/testing.go index df8f6d192cb..a61a0c6f8d9 100644 --- a/pkg/volume/testing/testing.go +++ b/pkg/volume/testing/testing.go @@ -33,8 +33,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" storagelisters "k8s.io/client-go/listers/storage/v1beta1" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" utiltesting "k8s.io/client-go/util/testing" cloudprovider "k8s.io/cloud-provider" @@ -68,11 +70,13 @@ type fakeVolumeHost struct { pluginMgr VolumePluginMgr cloud cloudprovider.Interface mounter mount.Interface + hostUtil mount.HostUtils exec mount.Exec nodeLabels map[string]string nodeName string subpather subpath.Interface csiDriverLister storagelisters.CSIDriverLister + informerFactory informers.SharedInformerFactory } var _ VolumeHost = &fakeVolumeHost{} @@ -103,12 +107,14 @@ func NewFakeVolumeHostWithCSINodeName(rootDir string, kubeClient clientset.Inter func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface, pathToTypeMap map[string]mount.FileType) *fakeVolumeHost { host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, cloud: cloud} - host.mounter = &mount.FakeMounter{ + host.mounter = &mount.FakeMounter{} + host.hostUtil = &mount.FakeHostUtil{ Filesystem: pathToTypeMap, } host.exec = mount.NewFakeExec(nil) host.pluginMgr.InitPlugins(plugins, nil /* prober */, host) host.subpather = &subpath.FakeSubpath{} + host.informerFactory = informers.NewSharedInformerFactory(kubeClient, time.Minute) return host } @@ -153,6 +159,10 @@ func (f *fakeVolumeHost) GetMounter(pluginName string) mount.Interface { return f.mounter } +func (f *fakeVolumeHost) GetHostUtil() mount.HostUtils { + return f.hostUtil +} + func (f *fakeVolumeHost) GetSubpather() subpath.Interface { return f.subpather } @@ -1495,11 +1505,28 @@ func (f *fakeVolumeHost) CSIDriverLister() storagelisters.CSIDriverLister { return f.csiDriverLister } +func (f *fakeVolumeHost) CSIDriversSynced() cache.InformerSynced { + // not needed for testing + return nil +} + func (f *fakeVolumeHost) CSINodeLister() storagelisters.CSINodeLister { // not needed for testing return nil } +func (f *fakeVolumeHost) GetInformerFactory() informers.SharedInformerFactory { + return f.informerFactory +} + func (f *fakeVolumeHost) IsAttachDetachController() bool { return true } + +func (f *fakeVolumeHost) SetKubeletError(err error) { + return +} + +func (f *fakeVolumeHost) WaitForCacheSync() error { + return nil +} diff --git a/pkg/volume/util/exec/exec_mount.go b/pkg/volume/util/exec/exec_mount.go index 1d2cfbdeac7..b8d3c1dc7f6 100644 --- a/pkg/volume/util/exec/exec_mount.go +++ b/pkg/volume/util/exec/exec_mount.go @@ -20,7 +20,6 @@ package exec import ( "fmt" - "os" "k8s.io/klog" @@ -92,70 +91,15 @@ func (m *execMounter) List() ([]mount.MountPoint, error) { return m.wrappedMounter.List() } +func (m *execMounter) IsMountPointMatch(mp mount.MountPoint, dir string) bool { + return m.wrappedMounter.IsMountPointMatch(mp, dir) +} + // IsLikelyNotMountPoint determines whether a path is a mountpoint. func (m *execMounter) IsLikelyNotMountPoint(file string) (bool, error) { return m.wrappedMounter.IsLikelyNotMountPoint(file) } -// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. -// Returns true if open returns errno EBUSY, and false if errno is nil. -// Returns an error if errno is any error other than EBUSY. -// Returns with error if pathname is not a device. -func (m *execMounter) DeviceOpened(pathname string) (bool, error) { - return m.wrappedMounter.DeviceOpened(pathname) -} - -// PathIsDevice uses FileInfo returned from os.Stat to check if path refers -// to a device. -func (m *execMounter) PathIsDevice(pathname string) (bool, error) { - return m.wrappedMounter.PathIsDevice(pathname) -} - -//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts -func (m *execMounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return m.wrappedMounter.GetDeviceNameFromMount(mountPath, pluginMountDir) -} - -func (m *execMounter) IsMountPointMatch(mp mount.MountPoint, dir string) bool { - return m.wrappedMounter.IsMountPointMatch(mp, dir) -} - -func (m *execMounter) MakeRShared(path string) error { - return m.wrappedMounter.MakeRShared(path) -} - -func (m *execMounter) GetFileType(pathname string) (mount.FileType, error) { - return m.wrappedMounter.GetFileType(pathname) -} - -func (m *execMounter) MakeFile(pathname string) error { - return m.wrappedMounter.MakeFile(pathname) -} - -func (m *execMounter) MakeDir(pathname string) error { - return m.wrappedMounter.MakeDir(pathname) -} - -func (m *execMounter) ExistsPath(pathname string) (bool, error) { - return m.wrappedMounter.ExistsPath(pathname) -} - -func (m *execMounter) EvalHostSymlinks(pathname string) (string, error) { - return m.wrappedMounter.EvalHostSymlinks(pathname) -} - func (m *execMounter) GetMountRefs(pathname string) ([]string, error) { return m.wrappedMounter.GetMountRefs(pathname) } - -func (m *execMounter) GetFSGroup(pathname string) (int64, error) { - return m.wrappedMounter.GetFSGroup(pathname) -} - -func (m *execMounter) GetSELinuxSupport(pathname string) (bool, error) { - return m.wrappedMounter.GetSELinuxSupport(pathname) -} - -func (m *execMounter) GetMode(pathname string) (os.FileMode, error) { - return m.wrappedMounter.GetMode(pathname) -} diff --git a/pkg/volume/util/exec/exec_mount_unsupported.go b/pkg/volume/util/exec/exec_mount_unsupported.go index ece0234b785..37ca41c3ef7 100644 --- a/pkg/volume/util/exec/exec_mount_unsupported.go +++ b/pkg/volume/util/exec/exec_mount_unsupported.go @@ -20,13 +20,14 @@ package exec import ( "errors" - "os" "k8s.io/kubernetes/pkg/util/mount" ) type execMounter struct{} +var _ = mount.Interface(&execMounter{}) + // NewExecMounter returns a mounter that uses provided Exec interface to mount and // unmount a filesystem. For all other calls it uses a wrapped mounter. func NewExecMounter(exec mount.Exec, wrapped mount.Interface) mount.Interface { @@ -53,54 +54,6 @@ func (mounter *execMounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } -func (mounter *execMounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return "", nil -} - -func (mounter *execMounter) DeviceOpened(pathname string) (bool, error) { - return false, nil -} - -func (mounter *execMounter) PathIsDevice(pathname string) (bool, error) { - return true, nil -} - -func (mounter *execMounter) MakeRShared(path string) error { - return nil -} - -func (mounter *execMounter) GetFileType(pathname string) (mount.FileType, error) { - return mount.FileType("fake"), errors.New("not implemented") -} - -func (mounter *execMounter) MakeDir(pathname string) error { - return nil -} - -func (mounter *execMounter) MakeFile(pathname string) error { - return nil -} - -func (mounter *execMounter) ExistsPath(pathname string) (bool, error) { - return true, errors.New("not implemented") -} - -func (mounter *execMounter) EvalHostSymlinks(pathname string) (string, error) { - return "", errors.New("not implemented") -} - func (mounter *execMounter) GetMountRefs(pathname string) ([]string, error) { return nil, errors.New("not implemented") } - -func (mounter *execMounter) GetFSGroup(pathname string) (int64, error) { - return -1, errors.New("not implemented") -} - -func (mounter *execMounter) GetSELinuxSupport(pathname string) (bool, error) { - return false, errors.New("not implemented") -} - -func (mounter *execMounter) GetMode(pathname string) (os.FileMode, error) { - return 0, errors.New("not implemented") -} diff --git a/pkg/volume/util/nsenter/BUILD b/pkg/volume/util/nsenter/BUILD index ab5b6aa7a8b..e4f27121bc1 100644 --- a/pkg/volume/util/nsenter/BUILD +++ b/pkg/volume/util/nsenter/BUILD @@ -65,6 +65,7 @@ go_test( embed = [":go_default_library"], deps = select({ "@io_bazel_rules_go//go/platform:linux": [ + "//pkg/util/mount:go_default_library", "//vendor/k8s.io/utils/nsenter:go_default_library", ], "//conditions:default": [], diff --git a/pkg/volume/util/nsenter/nsenter_mount.go b/pkg/volume/util/nsenter/nsenter_mount.go index f756dc4287b..001be408e7d 100644 --- a/pkg/volume/util/nsenter/nsenter_mount.go +++ b/pkg/volume/util/nsenter/nsenter_mount.go @@ -165,7 +165,8 @@ func (n *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { } // Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts - resolvedFile, err := n.EvalHostSymlinks(file) + hu := NewHostUtil(n.ne, n.rootDir) + resolvedFile, err := hu.EvalHostSymlinks(file) if err != nil { return true, err } @@ -211,36 +212,67 @@ func parseFindMnt(out string) (string, error) { return out[:i], nil } +// GetMountRefs finds all mount references to the path, returns a +// list of paths. Path could be a mountpoint path, device or a normal +// directory (for bind mount). +func (n *Mounter) GetMountRefs(pathname string) ([]string, error) { + pathExists, pathErr := mount.PathExists(pathname) + if !pathExists || mount.IsCorruptedMnt(pathErr) { + return []string{}, nil + } else if pathErr != nil { + return nil, fmt.Errorf("Error checking path %s: %v", pathname, pathErr) + } + hostpath, err := n.ne.EvalSymlinks(pathname, true /* mustExist */) + if err != nil { + return nil, err + } + return mount.SearchMountPoints(hostpath, hostProcMountinfoPath) +} + +type hostUtil struct { + ne *nsenter.Nsenter + rootDir string +} + +// hostUtil implements mount.HostUtils +var _ = mount.HostUtils(&hostUtil{}) + +// NewHostUtil returns a new mount.HostUtils implementation that works +// for kubelet running in a container +func NewHostUtil(ne *nsenter.Nsenter, rootDir string) mount.HostUtils { + return &hostUtil{ne: ne, rootDir: rootDir} +} + // DeviceOpened checks if block device in use by calling Open with O_EXCL flag. // Returns true if open returns errno EBUSY, and false if errno is nil. // Returns an error if errno is any error other than EBUSY. // Returns with error if pathname is not a device. -func (n *Mounter) DeviceOpened(pathname string) (bool, error) { +func (hu *hostUtil) DeviceOpened(pathname string) (bool, error) { return mount.ExclusiveOpenFailsOnDevice(pathname) } // PathIsDevice uses FileInfo returned from os.Stat to check if path refers // to a device. -func (n *Mounter) PathIsDevice(pathname string) (bool, error) { - pathType, err := n.GetFileType(pathname) +func (hu *hostUtil) PathIsDevice(pathname string) (bool, error) { + pathType, err := hu.GetFileType(pathname) isDevice := pathType == mount.FileTypeCharDev || pathType == mount.FileTypeBlockDev return isDevice, err } //GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts -func (n *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return mount.GetDeviceNameFromMountLinux(n, mountPath, pluginMountDir) +func (hu *hostUtil) GetDeviceNameFromMount(mounter mount.Interface, mountPath, pluginMountDir string) (string, error) { + return mount.GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir) } // MakeRShared checks if path is shared and bind-mounts it as rshared if needed. -func (n *Mounter) MakeRShared(path string) error { +func (hu *hostUtil) MakeRShared(path string) error { return mount.DoMakeRShared(path, hostProcMountinfoPath) } // GetFileType checks for file/directory/socket/block/character devices. -func (n *Mounter) GetFileType(pathname string) (mount.FileType, error) { +func (hu *hostUtil) GetFileType(pathname string) (mount.FileType, error) { var pathType mount.FileType - outputBytes, err := n.ne.Exec("stat", []string{"-L", "--printf=%F", pathname}).CombinedOutput() + outputBytes, err := hu.ne.Exec("stat", []string{"-L", "--printf=%F", pathname}).CombinedOutput() if err != nil { if strings.Contains(string(outputBytes), "No such file") { err = fmt.Errorf("%s does not exist", pathname) @@ -267,18 +299,18 @@ func (n *Mounter) GetFileType(pathname string) (mount.FileType, error) { } // MakeDir creates a new directory. -func (n *Mounter) MakeDir(pathname string) error { +func (hu *hostUtil) MakeDir(pathname string) error { args := []string{"-p", pathname} - if _, err := n.ne.Exec("mkdir", args).CombinedOutput(); err != nil { + if _, err := hu.ne.Exec("mkdir", args).CombinedOutput(); err != nil { return err } return nil } // MakeFile creates an empty file. -func (n *Mounter) MakeFile(pathname string) error { +func (hu *hostUtil) MakeFile(pathname string) error { args := []string{pathname} - if _, err := n.ne.Exec("touch", args).CombinedOutput(); err != nil { + if _, err := hu.ne.Exec("touch", args).CombinedOutput(); err != nil { return err } return nil @@ -286,60 +318,43 @@ func (n *Mounter) MakeFile(pathname string) error { // ExistsPath checks if pathname exists. // Error is returned on any other error than "file not found". -func (n *Mounter) ExistsPath(pathname string) (bool, error) { +func (hu *hostUtil) ExistsPath(pathname string) (bool, error) { // Resolve the symlinks but allow the target not to exist. EvalSymlinks // would return an generic error when the target does not exist. - hostPath, err := n.ne.EvalSymlinks(pathname, false /* mustExist */) + hostPath, err := hu.ne.EvalSymlinks(pathname, false /* mustExist */) if err != nil { return false, err } - kubeletpath := n.ne.KubeletPath(hostPath) + kubeletpath := hu.ne.KubeletPath(hostPath) return utilpath.Exists(utilpath.CheckFollowSymlink, kubeletpath) } // EvalHostSymlinks returns the path name after evaluating symlinks. -func (n *Mounter) EvalHostSymlinks(pathname string) (string, error) { - return n.ne.EvalSymlinks(pathname, true) -} - -// GetMountRefs finds all mount references to the path, returns a -// list of paths. Path could be a mountpoint path, device or a normal -// directory (for bind mount). -func (n *Mounter) GetMountRefs(pathname string) ([]string, error) { - pathExists, pathErr := mount.PathExists(pathname) - if !pathExists || mount.IsCorruptedMnt(pathErr) { - return []string{}, nil - } else if pathErr != nil { - return nil, fmt.Errorf("Error checking path %s: %v", pathname, pathErr) - } - hostpath, err := n.ne.EvalSymlinks(pathname, true /* mustExist */) - if err != nil { - return nil, err - } - return mount.SearchMountPoints(hostpath, hostProcMountinfoPath) +func (hu *hostUtil) EvalHostSymlinks(pathname string) (string, error) { + return hu.ne.EvalSymlinks(pathname, true) } // GetFSGroup returns FSGroup of pathname. -func (n *Mounter) GetFSGroup(pathname string) (int64, error) { - hostPath, err := n.ne.EvalSymlinks(pathname, true /* mustExist */) +func (hu *hostUtil) GetFSGroup(pathname string) (int64, error) { + hostPath, err := hu.ne.EvalSymlinks(pathname, true /* mustExist */) if err != nil { return -1, err } - kubeletpath := n.ne.KubeletPath(hostPath) + kubeletpath := hu.ne.KubeletPath(hostPath) return mount.GetFSGroupLinux(kubeletpath) } // GetSELinuxSupport tests if pathname is on a mount that supports SELinux. -func (n *Mounter) GetSELinuxSupport(pathname string) (bool, error) { +func (hu *hostUtil) GetSELinuxSupport(pathname string) (bool, error) { return mount.GetSELinux(pathname, hostProcMountsPath) } // GetMode returns permissions of pathname. -func (n *Mounter) GetMode(pathname string) (os.FileMode, error) { - hostPath, err := n.ne.EvalSymlinks(pathname, true /* mustExist */) +func (hu *hostUtil) GetMode(pathname string) (os.FileMode, error) { + hostPath, err := hu.ne.EvalSymlinks(pathname, true /* mustExist */) if err != nil { return 0, err } - kubeletpath := n.ne.KubeletPath(hostPath) + kubeletpath := hu.ne.KubeletPath(hostPath) return mount.GetModeLinux(kubeletpath) } diff --git a/pkg/volume/util/nsenter/nsenter_mount_test.go b/pkg/volume/util/nsenter/nsenter_mount_test.go index 6124673a671..634f417f8d6 100644 --- a/pkg/volume/util/nsenter/nsenter_mount_test.go +++ b/pkg/volume/util/nsenter/nsenter_mount_test.go @@ -25,6 +25,7 @@ import ( "path/filepath" "testing" + "k8s.io/kubernetes/pkg/util/mount" "k8s.io/utils/nsenter" ) @@ -74,8 +75,9 @@ func TestParseFindMnt(t *testing.T) { } } -func newFakeNsenterMounter(tmpdir string, t *testing.T) (mounter *Mounter, rootfsPath string, varlibPath string, err error) { - rootfsPath = filepath.Join(tmpdir, "rootfs") +func newFakeNsenterHostUtil(tmpdir string, t *testing.T) (mount.HostUtils, string, string, error) { + rootfsPath := filepath.Join(tmpdir, "rootfs") + if err := os.Mkdir(rootfsPath, 0755); err != nil { return nil, "", "", err } @@ -84,12 +86,14 @@ func newFakeNsenterMounter(tmpdir string, t *testing.T) (mounter *Mounter, rootf return nil, "", "", err } - varlibPath = filepath.Join(tmpdir, "/var/lib/kubelet") + varlibPath := filepath.Join(tmpdir, "var/lib/kubelet") if err := os.MkdirAll(varlibPath, 0755); err != nil { return nil, "", "", err } - return NewMounter(varlibPath, ne), rootfsPath, varlibPath, nil + hu := NewHostUtil(ne, varlibPath) + + return hu, rootfsPath, varlibPath, nil } func TestNsenterExistsFile(t *testing.T) { @@ -262,7 +266,7 @@ func TestNsenterExistsFile(t *testing.T) { continue } - mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t) + hu, rootfs, _, err := newFakeNsenterHostUtil(tmpdir, t) if err != nil { t.Error(err) continue @@ -274,7 +278,7 @@ func TestNsenterExistsFile(t *testing.T) { continue } - out, err := mounter.ExistsPath(path) + out, err := hu.ExistsPath(path) if err != nil && !test.expectError { t.Errorf("Test %q: unexpected error: %s", test.name, err) } @@ -387,7 +391,7 @@ func TestNsenterGetMode(t *testing.T) { continue } - mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t) + hu, rootfs, _, err := newFakeNsenterHostUtil(tmpdir, t) if err != nil { t.Error(err) continue @@ -399,7 +403,7 @@ func TestNsenterGetMode(t *testing.T) { continue } - mode, err := mounter.GetMode(path) + mode, err := hu.GetMode(path) if err != nil && !test.expectError { t.Errorf("Test %q: unexpected error: %s", test.name, err) } diff --git a/pkg/volume/util/nsenter/nsenter_mount_unsupported.go b/pkg/volume/util/nsenter/nsenter_mount_unsupported.go index 2ce12ad3a5e..e76a0667672 100644 --- a/pkg/volume/util/nsenter/nsenter_mount_unsupported.go +++ b/pkg/volume/util/nsenter/nsenter_mount_unsupported.go @@ -65,75 +65,87 @@ func (*Mounter) IsLikelyNotMountPoint(file string) (bool, error) { return true, nil } -// DeviceOpened checks if block device in use. I tis a noop for unsupported systems -func (*Mounter) DeviceOpened(pathname string) (bool, error) { - return false, nil -} - -// PathIsDevice checks if pathname refers to a device. It is a noop for unsupported -// systems -func (*Mounter) PathIsDevice(pathname string) (bool, error) { - return true, nil -} - -// GetDeviceNameFromMount finds the device name from its global mount point using the -// given mountpath and plugin location. It is a noop of unsupported platforms -func (*Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) { - return "", nil -} - -// MakeRShared checks if path is shared and bind-mounts it as rshared if needed. -// It is a noop on unsupported platforms -func (*Mounter) MakeRShared(path string) error { - return nil -} - -// GetFileType checks for file/directory/socket/block/character devices. -// Always returns an error and "fake" filetype on unsupported platforms -func (*Mounter) GetFileType(_ string) (mount.FileType, error) { - return mount.FileType("fake"), errors.New("not implemented") -} - -// MakeDir creates a new directory. Noop on unsupported platforms -func (*Mounter) MakeDir(pathname string) error { - return nil -} - -// MakeFile creats an empty file. Noop on unsupported platforms -func (*Mounter) MakeFile(pathname string) error { - return nil -} - -// ExistsPath checks if pathname exists. Always returns an error on unsupported -// platforms -func (*Mounter) ExistsPath(pathname string) (bool, error) { - return true, errors.New("not implemented") -} - -// EvalHostSymlinks returns the path name after evaluating symlinks. Always -// returns an error on unsupported platforms -func (*Mounter) EvalHostSymlinks(pathname string) (string, error) { - return "", errors.New("not implemented") -} - // GetMountRefs finds all mount references to the path, returns a // list of paths. Always returns an error on unsupported platforms func (*Mounter) GetMountRefs(pathname string) ([]string, error) { return nil, errors.New("not implemented") } +type hostUtil struct { +} + +// hostUtil implements mount.HostUtils +var _ = mount.HostUtils(&hostUtil{}) + +// NewHostUtil returns a new implementation of mount.HostUtils for unsupported +// platforms +func NewHostUtil(ne *nsenter.Nsenter, rootDir string) mount.HostUtils { + return &hostUtil{} +} + +// DeviceOpened checks if block device in use. I tis a noop for unsupported systems +func (*hostUtil) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +// PathIsDevice checks if pathname refers to a device. It is a noop for unsupported +// systems +func (*hostUtil) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +// GetDeviceNameFromMount finds the device name from its global mount point using the +// given mountpath and plugin location. It is a noop of unsupported platforms +func (*hostUtil) GetDeviceNameFromMount(mounter mount.Interface, mountPath, pluginMountDir string) (string, error) { + return "", nil +} + +// MakeRShared checks if path is shared and bind-mounts it as rshared if needed. +// It is a noop on unsupported platforms +func (*hostUtil) MakeRShared(path string) error { + return nil +} + +// GetFileType checks for file/directory/socket/block/character devices. +// Always returns an error and "fake" filetype on unsupported platforms +func (*hostUtil) GetFileType(_ string) (mount.FileType, error) { + return mount.FileType("fake"), errors.New("not implemented") +} + +// MakeDir creates a new directory. Noop on unsupported platforms +func (*hostUtil) MakeDir(pathname string) error { + return nil +} + +// MakeFile creats an empty file. Noop on unsupported platforms +func (*hostUtil) MakeFile(pathname string) error { + return nil +} + +// ExistsPath checks if pathname exists. Always returns an error on unsupported +// platforms +func (*hostUtil) ExistsPath(pathname string) (bool, error) { + return true, errors.New("not implemented") +} + +// EvalHostSymlinks returns the path name after evaluating symlinks. Always +// returns an error on unsupported platforms +func (*hostUtil) EvalHostSymlinks(pathname string) (string, error) { + return "", errors.New("not implemented") +} + // GetFSGroup returns FSGroup of pathname. Always returns an error on unsupported platforms -func (*Mounter) GetFSGroup(pathname string) (int64, error) { +func (*hostUtil) GetFSGroup(pathname string) (int64, error) { return -1, errors.New("not implemented") } // GetSELinuxSupport tests if pathname is on a mount that supports SELinux. // Always returns an error on unsupported platforms -func (*Mounter) GetSELinuxSupport(pathname string) (bool, error) { +func (*hostUtil) GetSELinuxSupport(pathname string) (bool, error) { return false, errors.New("not implemented") } // GetMode returns permissions of pathname. Always returns an error on unsupported platforms -func (*Mounter) GetMode(pathname string) (os.FileMode, error) { +func (*hostUtil) GetMode(pathname string) (os.FileMode, error) { return 0, errors.New("not implemented") } diff --git a/pkg/volume/util/operationexecutor/fakegenerator.go b/pkg/volume/util/operationexecutor/fakegenerator.go index e41b73d1556..ca317acc85a 100644 --- a/pkg/volume/util/operationexecutor/fakegenerator.go +++ b/pkg/volume/util/operationexecutor/fakegenerator.go @@ -64,7 +64,7 @@ func (f *fakeOGCounter) GenerateVolumesAreAttachedFunc(attachedVolumes []Attache return f.recordFuncCall("GenerateVolumesAreAttachedFunc"), nil } -func (f *fakeOGCounter) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { +func (f *fakeOGCounter) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hu mount.HostUtils) (volumetypes.GeneratedOperations, error) { return f.recordFuncCall("GenerateUnmountDeviceFunc"), nil } @@ -80,7 +80,7 @@ func (f *fakeOGCounter) GenerateUnmapVolumeFunc(volumeToUnmount MountedVolume, a return f.recordFuncCall("GenerateUnmapVolumeFunc"), nil } -func (f *fakeOGCounter) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { +func (f *fakeOGCounter) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hu mount.HostUtils) (volumetypes.GeneratedOperations, error) { return f.recordFuncCall("GenerateUnmapDeviceFunc"), nil } diff --git a/pkg/volume/util/operationexecutor/operation_executor.go b/pkg/volume/util/operationexecutor/operation_executor.go index fce380d6b32..4a93472e62d 100644 --- a/pkg/volume/util/operationexecutor/operation_executor.go +++ b/pkg/volume/util/operationexecutor/operation_executor.go @@ -123,7 +123,7 @@ type OperationExecutor interface { // global map path. If number of reference is zero, remove global map path // directory and free a volume for detach. // It then updates the actual state of the world to reflect that. - UnmountDevice(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error + UnmountDevice(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hostutil mount.HostUtils) error // VerifyControllerAttachedVolume checks if the specified volume is present // in the specified nodes AttachedVolumes Status field. It uses kubeClient @@ -792,7 +792,7 @@ func (oe *operationExecutor) UnmountVolume( func (oe *operationExecutor) UnmountDevice( deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, - mounter mount.Interface) error { + hostutil mount.HostUtils) error { fsVolume, err := util.CheckVolumeModeFilesystem(deviceToDetach.VolumeSpec) if err != nil { return err @@ -802,12 +802,12 @@ func (oe *operationExecutor) UnmountDevice( // Filesystem volume case // Unmount and detach a device if a volume isn't referenced generatedOperations, err = oe.operationGenerator.GenerateUnmountDeviceFunc( - deviceToDetach, actualStateOfWorld, mounter) + deviceToDetach, actualStateOfWorld, hostutil) } else { // Block volume case // Detach a device and remove loopback if a volume isn't referenced generatedOperations, err = oe.operationGenerator.GenerateUnmapDeviceFunc( - deviceToDetach, actualStateOfWorld, mounter) + deviceToDetach, actualStateOfWorld, hostutil) } if err != nil { return err diff --git a/pkg/volume/util/operationexecutor/operation_executor_test.go b/pkg/volume/util/operationexecutor/operation_executor_test.go index a1036987c7e..eec9c975530 100644 --- a/pkg/volume/util/operationexecutor/operation_executor_test.go +++ b/pkg/volume/util/operationexecutor/operation_executor_test.go @@ -433,7 +433,7 @@ func (fopg *fakeOperationGenerator) GenerateVolumesAreAttachedFunc(attachedVolum OperationFunc: opFunc, }, nil } -func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { +func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hostutil mount.HostUtils) (volumetypes.GeneratedOperations, error) { opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) return nil, nil @@ -506,7 +506,7 @@ func (fopg *fakeOperationGenerator) GenerateUnmapVolumeFunc(volumeToUnmount Moun }, nil } -func (fopg *fakeOperationGenerator) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) { +func (fopg *fakeOperationGenerator) GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, hostutil mount.HostUtils) (volumetypes.GeneratedOperations, error) { opFunc := func() (error, error) { startOperationAndBlock(fopg.ch, fopg.quit) return nil, nil diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 7428de7c534..34a0e8a6533 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -106,7 +106,7 @@ type OperationGenerator interface { GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) // Generates the UnMountDevice function needed to perform the unmount of a device - GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) + GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.HostUtils) (volumetypes.GeneratedOperations, error) // Generates the function needed to check if the attach_detach controller has attached the volume plugin GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error) @@ -118,7 +118,7 @@ type OperationGenerator interface { GenerateUnmapVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) // Generates the UnmapDevice function needed to perform the unmap of a device - GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (volumetypes.GeneratedOperations, error) + GenerateUnmapDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.HostUtils) (volumetypes.GeneratedOperations, error) // GetVolumePluginMgr returns volume plugin manager GetVolumePluginMgr() *volume.VolumePluginMgr @@ -888,7 +888,7 @@ func (og *operationGenerator) GenerateUnmountVolumeFunc( func (og *operationGenerator) GenerateUnmountDeviceFunc( deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, - mounter mount.Interface) (volumetypes.GeneratedOperations, error) { + hostutil mount.HostUtils) (volumetypes.GeneratedOperations, error) { var pluginName string if useCSIPlugin(og.volumePluginMgr, deviceToDetach.VolumeSpec) { @@ -942,10 +942,10 @@ func (og *operationGenerator) GenerateUnmountDeviceFunc( return deviceToDetach.GenerateError("UnmountDevice failed", unmountDeviceErr) } // Before logging that UnmountDevice succeeded and moving on, - // use mounter.PathIsDevice to check if the path is a device, - // if so use mounter.DeviceOpened to check if the device is in use anywhere + // use hostutil.PathIsDevice to check if the path is a device, + // if so use hostutil.DeviceOpened to check if the device is in use anywhere // else on the system. Retry if it returns true. - deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, mounter) + deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, hostutil) if deviceOpenedErr != nil { return nil, deviceOpenedErr } @@ -1080,8 +1080,12 @@ func (og *operationGenerator) GenerateMapVolumeFunc( // kubelet, so evaluate it on the host and expect that it links to a device in /dev, // which will be available to containerized kubelet. If still it does not exist, // AttachFileDevice will fail. If kubelet is not containerized, eval it anyway. - mounter := og.GetVolumePluginMgr().Host.GetMounter(blockVolumePlugin.GetPluginName()) - devicePath, err = mounter.EvalHostSymlinks(devicePath) + kvh, ok := og.GetVolumePluginMgr().Host.(volume.KubeletVolumeHost) + if !ok { + return volumeToMount.GenerateError("MapVolume type assertion error", fmt.Errorf("volume host does not implement KubeletVolumeHost interface")) + } + hu := kvh.GetHostUtil() + devicePath, err = hu.EvalHostSymlinks(devicePath) if err != nil { return volumeToMount.GenerateError("MapVolume.EvalHostSymlinks failed", err) } @@ -1258,7 +1262,7 @@ func (og *operationGenerator) GenerateUnmapVolumeFunc( func (og *operationGenerator) GenerateUnmapDeviceFunc( deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, - mounter mount.Interface) (volumetypes.GeneratedOperations, error) { + hostutil mount.HostUtils) (volumetypes.GeneratedOperations, error) { var blockVolumePlugin volume.BlockVolumePlugin var err error @@ -1344,10 +1348,10 @@ func (og *operationGenerator) GenerateUnmapDeviceFunc( } // Before logging that UnmapDevice succeeded and moving on, - // use mounter.PathIsDevice to check if the path is a device, - // if so use mounter.DeviceOpened to check if the device is in use anywhere + // use hostutil.PathIsDevice to check if the path is a device, + // if so use hostutil.DeviceOpened to check if the device is in use anywhere // else on the system. Retry if it returns true. - deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, mounter) + deviceOpened, deviceOpenedErr := isDeviceOpened(deviceToDetach, hostutil) if deviceOpenedErr != nil { return nil, deviceOpenedErr } @@ -1726,8 +1730,8 @@ func checkNodeAffinity(og *operationGenerator, volumeToMount VolumeToMount) erro } // isDeviceOpened checks the device status if the device is in use anywhere else on the system -func isDeviceOpened(deviceToDetach AttachedVolume, mounter mount.Interface) (bool, error) { - isDevicePath, devicePathErr := mounter.PathIsDevice(deviceToDetach.DevicePath) +func isDeviceOpened(deviceToDetach AttachedVolume, hostUtil mount.HostUtils) (bool, error) { + isDevicePath, devicePathErr := hostUtil.PathIsDevice(deviceToDetach.DevicePath) var deviceOpened bool var deviceOpenedErr error if !isDevicePath && devicePathErr == nil || @@ -1739,7 +1743,7 @@ func isDeviceOpened(deviceToDetach AttachedVolume, mounter mount.Interface) (boo } else if devicePathErr != nil { return false, deviceToDetach.GenerateErrorDetailed("PathIsDevice failed", devicePathErr) } else { - deviceOpened, deviceOpenedErr = mounter.DeviceOpened(deviceToDetach.DevicePath) + deviceOpened, deviceOpenedErr = hostUtil.DeviceOpened(deviceToDetach.DevicePath) if deviceOpenedErr != nil { return false, deviceToDetach.GenerateErrorDetailed("DeviceOpened failed", deviceOpenedErr) } diff --git a/pkg/volume/vsphere_volume/vsphere_volume.go b/pkg/volume/vsphere_volume/vsphere_volume.go index 30c8a8d09d9..8dc860b9c0e 100644 --- a/pkg/volume/vsphere_volume/vsphere_volume.go +++ b/pkg/volume/vsphere_volume/vsphere_volume.go @@ -145,8 +145,13 @@ func (plugin *vsphereVolumePlugin) newUnmounterInternal(volName string, podUID t func (plugin *vsphereVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { mounter := plugin.host.GetMounter(plugin.GetPluginName()) + kvh, ok := plugin.host.(volume.KubeletVolumeHost) + if !ok { + return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") + } + hu := kvh.GetHostUtil() pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) - volumePath, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir) + volumePath, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) if err != nil { return nil, err }