diff --git a/pkg/util/mount/BUILD b/pkg/util/mount/BUILD index 023fad7ba35..c966015d9cb 100644 --- a/pkg/util/mount/BUILD +++ b/pkg/util/mount/BUILD @@ -17,6 +17,7 @@ go_library( "nsenter_mount_unsupported.go", ] + select({ "@io_bazel_rules_go//go/platform:linux_amd64": [ + "exec_mount.go", "mount_linux.go", "nsenter_mount.go", ], @@ -46,6 +47,7 @@ go_test( "safe_format_and_mount_test.go", ] + select({ "@io_bazel_rules_go//go/platform:linux_amd64": [ + "exec_mount_test.go", "mount_linux_test.go", "nsenter_mount_test.go", ], diff --git a/pkg/util/mount/exec_mount.go b/pkg/util/mount/exec_mount.go new file mode 100644 index 00000000000..1dedc5b7aea --- /dev/null +++ b/pkg/util/mount/exec_mount.go @@ -0,0 +1,140 @@ +// +build linux + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mount + +import ( + "fmt" + + "github.com/golang/glog" +) + +// ExecMounter is a mounter that uses provided Exec interface to mount and +// unmount a filesystem. For all other calls it uses a wrapped mounter. +type execMounter struct { + wrappedMounter Interface + exec Exec +} + +func NewExecMounter(exec Exec, wrapped Interface) Interface { + return &execMounter{ + wrappedMounter: wrapped, + exec: exec, + } +} + +// execMounter implements mount.Interface +var _ Interface = &execMounter{} + +// Mount runs mount(8) using given exec interface. +func (m *execMounter) Mount(source string, target string, fstype string, options []string) error { + bind, bindRemountOpts := isBind(options) + + if bind { + err := m.doExecMount(source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return m.doExecMount(source, target, fstype, bindRemountOpts) + } + + return m.doExecMount(source, target, fstype, options) +} + +// doExecMount calls exec(mount ) using given exec interface. +func (m *execMounter) doExecMount(source, target, fstype string, options []string) error { + glog.V(5).Infof("Exec Mounting %s %s %s %v", source, target, fstype, options) + mountArgs := makeMountArgs(source, target, fstype, options) + output, err := m.exec.Run("mount", mountArgs...) + glog.V(5).Infof("Exec mounted %v: %v: %s", mountArgs, err, string(output)) + if err != nil { + return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", + err, "mount", source, target, fstype, options, string(output)) + } + + return err +} + +// Unmount runs umount(8) using given exec interface. +func (m *execMounter) Unmount(target string) error { + outputBytes, err := m.exec.Run("umount", target) + if err == nil { + glog.V(5).Infof("Exec unmounted %s: %s", target, string(outputBytes)) + } else { + glog.V(5).Infof("Failed to exec unmount %s: err: %q, umount output: %s", target, err, string(outputBytes)) + } + + return err +} + +// List returns a list of all mounted filesystems. +func (m *execMounter) List() ([]MountPoint, error) { + return m.wrappedMounter.List() +} + +// 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, pluginDir string) (string, error) { + return m.wrappedMounter.GetDeviceNameFromMount(mountPath, pluginDir) +} + +func (m *execMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return m.wrappedMounter.IsMountPointMatch(mp, dir) +} + +func (m *execMounter) IsNotMountPoint(dir string) (bool, error) { + return m.wrappedMounter.IsNotMountPoint(dir) +} + +func (m *execMounter) MakeRShared(path string) error { + return m.wrappedMounter.MakeRShared(path) +} + +func (m *execMounter) GetFileType(pathname string) (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 { + return m.wrappedMounter.ExistsPath(pathname) +} diff --git a/pkg/util/mount/exec_mount_test.go b/pkg/util/mount/exec_mount_test.go new file mode 100644 index 00000000000..5882477f71e --- /dev/null +++ b/pkg/util/mount/exec_mount_test.go @@ -0,0 +1,153 @@ +// +build linux + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mount + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +var ( + sourcePath = "/mnt/srv" + destinationPath = "/mnt/dst" + fsType = "xfs" + mountOptions = []string{"vers=1", "foo=bar"} +) + +func TestMount(t *testing.T) { + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + if cmd != "mount" { + t.Errorf("expected mount command, got %q", cmd) + } + // mount -t fstype -o options source target + expectedArgs := []string{"-t", fsType, "-o", strings.Join(mountOptions, ","), sourcePath, destinationPath} + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + + mounter.Mount(sourcePath, destinationPath, fsType, mountOptions) +} + +func TestBindMount(t *testing.T) { + cmdCount := 0 + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + cmdCount++ + if cmd != "mount" { + t.Errorf("expected mount command, got %q", cmd) + } + var expectedArgs []string + switch cmdCount { + case 1: + // mount -t fstype -o "bind" source target + expectedArgs = []string{"-t", fsType, "-o", "bind", sourcePath, destinationPath} + case 2: + // mount -t fstype -o "remount,opts" source target + expectedArgs = []string{"-t", fsType, "-o", "remount," + strings.Join(mountOptions, ","), sourcePath, destinationPath} + } + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + bindOptions := append(mountOptions, "bind") + mounter.Mount(sourcePath, destinationPath, fsType, bindOptions) +} + +func TestUnmount(t *testing.T) { + exec := NewFakeExec(func(cmd string, args ...string) ([]byte, error) { + if cmd != "umount" { + t.Errorf("expected unmount command, got %q", cmd) + } + // unmount $target + expectedArgs := []string{destinationPath} + if !reflect.DeepEqual(expectedArgs, args) { + t.Errorf("expected arguments %q, got %q", strings.Join(expectedArgs, " "), strings.Join(args, " ")) + } + return nil, nil + }) + + wrappedMounter := &fakeMounter{t} + mounter := NewExecMounter(exec, wrappedMounter) + + mounter.Unmount(destinationPath) +} + +/* Fake wrapped mounter */ +type fakeMounter struct { + t *testing.T +} + +func (fm *fakeMounter) Mount(source string, target string, fstype string, options []string) error { + // Mount() of wrapped mounter should never be called. We call exec instead. + fm.t.Errorf("Unexpected wrapped mount call") + return fmt.Errorf("Unexpected wrapped mount call") +} + +func (fm *fakeMounter) Unmount(target string) error { + // umount() of wrapped mounter should never be called. We call exec instead. + fm.t.Errorf("Unexpected wrapped mount call") + return fmt.Errorf("Unexpected wrapped mount call") +} + +func (fm *fakeMounter) List() ([]MountPoint, error) { + return nil, nil +} +func (fm *fakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return false +} +func (fm *fakeMounter) IsNotMountPoint(file string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) PathIsDevice(pathname string) (bool, error) { + return false, nil +} +func (fm *fakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} +func (fm *fakeMounter) MakeRShared(path string) error { + return nil +} +func (fm *fakeMounter) MakeFile(pathname string) error { + return nil +} +func (fm *fakeMounter) MakeDir(pathname string) error { + return nil +} +func (fm *fakeMounter) ExistsPath(pathname string) bool { + return false +} +func (fm *fakeMounter) GetFileType(pathname string) (FileType, error) { + return FileTypeFile, nil +}