Merge pull request #6400 from ddysher/nfs-volume-mounter

Implement Mount interface using mount(8) and umount(8)
This commit is contained in:
Vish Kannan 2015-04-30 14:00:56 -07:00
commit 1dead9c670
20 changed files with 188 additions and 238 deletions

View File

@ -38,13 +38,13 @@ func (f *FakeMounter) ResetLog() {
f.Log = []FakeAction{} f.Log = []FakeAction{}
} }
func (f *FakeMounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { func (f *FakeMounter) Mount(source string, target string, fstype string, options []string) error {
f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: target, Type: fstype}) f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: target, Type: fstype})
f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: target, Source: source, FSType: fstype}) f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: target, Source: source, FSType: fstype})
return nil return nil
} }
func (f *FakeMounter) Unmount(target string, flags int) error { func (f *FakeMounter) Unmount(target string) error {
newMountpoints := []MountPoint{} newMountpoints := []MountPoint{}
for _, mp := range f.MountPoints { for _, mp := range f.MountPoints {
if mp.Path != target { if mp.Path != target {

View File

@ -18,14 +18,11 @@ limitations under the License.
// an alternate platform, we will need to abstract further. // an alternate platform, we will need to abstract further.
package mount package mount
// Each supported platform must define the following flags:
// - FlagBind: specifies a bind mount
// - FlagReadOnly: the mount will be read-only
type Interface interface { type Interface interface {
// Mount wraps syscall.Mount(). // Mount mounts source to target as fstype with given options.
Mount(source string, target string, fstype string, flags uintptr, data string) error Mount(source string, target string, fstype string, options []string) error
// Umount wraps syscall.Mount(). // Unmount unmounts given target.
Unmount(target string, flags int) error Unmount(target string) error
// List returns a list of all mounted filesystems. This can be large. // List returns a list of all mounted filesystems. This can be large.
// On some platforms, reading mounts is not guaranteed consistent (i.e. // On some platforms, reading mounts is not guaranteed consistent (i.e.
// it could change between chunked reads). This is guaranteed to be // it could change between chunked reads). This is guaranteed to be

View File

@ -24,6 +24,7 @@ import (
"hash/adler32" "hash/adler32"
"io" "io"
"os" "os"
"os/exec"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -31,28 +32,91 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
const FlagBind = syscall.MS_BIND const (
const FlagReadOnly = syscall.MS_RDONLY // How many times to retry for a consistent read of /proc/mounts.
maxListTries = 3
// Number of fields per line in "/proc/mounts", as per the fstab man page.
expectedNumFieldsPerLine = 6
)
// Mounter implements mount.Interface for linux platform. // Mounter implements mount.Interface for linux platform.
type Mounter struct{} type Mounter struct{}
// Mount wraps syscall.Mount() // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
func (mounter *Mounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { // be an emtpy string in case it's not required, e.g. for remount, or for auto filesystem
glog.V(5).Infof("Mounting %s %s %s %d %s", source, target, fstype, flags, data) // type, where kernel handles fs type for you. The mount 'options' is a list of options,
return syscall.Mount(source, target, fstype, flags, data) // currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
// required, call Mount with an empty string list or nil.
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
// The remount options to use in case of bind mount, due to the fact that bind mount doesn't
// respect mount options. The list equals:
// options - 'bind' + 'remount' (no duplicate)
bindRemountOpts := []string{"remount"}
bind := false
if len(options) != 0 {
for _, option := range options {
switch option {
case "bind":
bind = true
break
case "remount":
break
default:
bindRemountOpts = append(bindRemountOpts, option)
}
}
} }
// Unmount wraps syscall.Unmount() if bind {
func (mounter *Mounter) Unmount(target string, flags int) error { err := doMount(source, target, fstype, []string{"bind"})
return syscall.Unmount(target, flags) if err != nil {
return err
}
return doMount(source, target, fstype, bindRemountOpts)
} else {
return doMount(source, target, fstype, options)
}
} }
// How many times to retry for a consistent read of /proc/mounts. func doMount(source string, target string, fstype string, options []string) error {
const maxListTries = 3 glog.V(5).Infof("Mounting %s %s %s %v", source, target, fstype, options)
// Build mount command as follows:
// mount [-t $fstype] [-o $options] [$source] $target
mountArgs := []string{}
if len(fstype) > 0 {
mountArgs = append(mountArgs, "-t", fstype)
}
if len(options) > 0 {
mountArgs = append(mountArgs, "-o", strings.Join(options, ","))
}
if len(source) > 0 {
mountArgs = append(mountArgs, source)
}
mountArgs = append(mountArgs, target)
command := exec.Command("mount", mountArgs...)
output, err := command.CombinedOutput()
if err != nil {
glog.Errorf("Mount failed: %v\nMounting arguments: %s %s %s %v\nOutput: %s\n",
err, source, target, fstype, options, string(output))
}
return err
}
// Unmount unmounts target with given options.
func (mounter *Mounter) Unmount(target string) error {
glog.V(5).Infof("Unmounting %s %v")
command := exec.Command("umount", target)
output, err := command.CombinedOutput()
if err != nil {
glog.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output))
return err
}
return nil
}
// List returns a list of all mounted filesystems. // List returns a list of all mounted filesystems.
func (*Mounter) List() ([]MountPoint, error) { func (mounter *Mounter) List() ([]MountPoint, error) {
hash1, err := readProcMounts(nil) hash1, err := readProcMounts(nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -89,9 +153,6 @@ func (mounter *Mounter) IsMountPoint(file string) (bool, error) {
return stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev, nil return stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev, nil
} }
// As per the fstab man page.
const expectedNumFieldsPerLine = 6
// readProcMounts reads /proc/mounts and produces a hash of the contents. If the out // readProcMounts reads /proc/mounts and produces a hash of the contents. If the out
// argument is not nil, this fills it with MountPoint structs. // argument is not nil, this fills it with MountPoint structs.
func readProcMounts(out *[]MountPoint) (uint32, error) { func readProcMounts(out *[]MountPoint) (uint32, error) {

View File

@ -18,16 +18,13 @@ limitations under the License.
package mount package mount
const FlagBind = 0
const FlagReadOnly = 0
type Mounter struct{} type Mounter struct{}
func (mounter *Mounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
return nil return nil
} }
func (mounter *Mounter) Unmount(target string, flags int) error { func (mounter *Mounter) Unmount(target string) error {
return nil return nil
} }

View File

@ -192,11 +192,6 @@ func (pd *awsElasticBlockStore) SetUpAt(dir string) error {
return err return err
} }
flags := uintptr(0)
if pd.readOnly {
flags = mount.FlagReadOnly
}
if err := os.MkdirAll(dir, 0750); err != nil { if err := os.MkdirAll(dir, 0750); err != nil {
// TODO: we should really eject the attach/detach out into its own control loop. // TODO: we should really eject the attach/detach out into its own control loop.
detachDiskLogError(pd) detachDiskLogError(pd)
@ -204,7 +199,11 @@ func (pd *awsElasticBlockStore) SetUpAt(dir string) error {
} }
// Perform a bind mount to the full path to allow duplicate mounts of the same PD. // Perform a bind mount to the full path to allow duplicate mounts of the same PD.
err = pd.mounter.Mount(globalPDPath, dir, "", mount.FlagBind|flags, "") options := []string{"bind"}
if pd.readOnly {
options = append(options, "ro")
}
err = pd.mounter.Mount(globalPDPath, dir, "", options)
if err != nil { if err != nil {
mountpoint, mntErr := pd.mounter.IsMountPoint(dir) mountpoint, mntErr := pd.mounter.IsMountPoint(dir)
if mntErr != nil { if mntErr != nil {
@ -212,7 +211,7 @@ func (pd *awsElasticBlockStore) SetUpAt(dir string) error {
return err return err
} }
if mountpoint { if mountpoint {
if mntErr = pd.mounter.Unmount(dir, 0); mntErr != nil { if mntErr = pd.mounter.Unmount(dir); mntErr != nil {
glog.Errorf("Failed to unmount: %v", mntErr) glog.Errorf("Failed to unmount: %v", mntErr)
return err return err
} }
@ -291,7 +290,7 @@ func (pd *awsElasticBlockStore) TearDownAt(dir string) error {
return err return err
} }
// Unmount the bind-mount inside this pod // Unmount the bind-mount inside this pod
if err := pd.mounter.Unmount(dir, 0); err != nil { if err := pd.mounter.Unmount(dir); err != nil {
glog.V(2).Info("Error unmounting dir ", dir, ": ", err) glog.V(2).Info("Error unmounting dir ", dir, ": ", err)
return err return err
} }

View File

@ -36,10 +36,6 @@ func (util *AWSDiskUtil) AttachAndMountDisk(pd *awsElasticBlockStore, globalPDPa
if err != nil { if err != nil {
return err return err
} }
flags := uintptr(0)
if pd.readOnly {
flags = mount.FlagReadOnly
}
devicePath, err := volumes.AttachDisk("", pd.volumeID, pd.readOnly) devicePath, err := volumes.AttachDisk("", pd.volumeID, pd.readOnly)
if err != nil { if err != nil {
return err return err
@ -76,8 +72,12 @@ func (util *AWSDiskUtil) AttachAndMountDisk(pd *awsElasticBlockStore, globalPDPa
return err return err
} }
} }
options := []string{}
if pd.readOnly {
options = append(options, "ro")
}
if !mountpoint { if !mountpoint {
err = pd.diskMounter.Mount(devicePath, globalPDPath, pd.fsType, flags, "") err = pd.diskMounter.Mount(devicePath, globalPDPath, pd.fsType, options)
if err != nil { if err != nil {
os.Remove(globalPDPath) os.Remove(globalPDPath)
return err return err
@ -90,7 +90,7 @@ func (util *AWSDiskUtil) AttachAndMountDisk(pd *awsElasticBlockStore, globalPDPa
func (util *AWSDiskUtil) DetachDisk(pd *awsElasticBlockStore) error { func (util *AWSDiskUtil) DetachDisk(pd *awsElasticBlockStore) error {
// Unmount the global PD mount, which should be the only one. // Unmount the global PD mount, which should be the only one.
globalPDPath := makeGlobalPDPath(pd.plugin.host, pd.volumeID) globalPDPath := makeGlobalPDPath(pd.plugin.host, pd.volumeID)
if err := pd.mounter.Unmount(globalPDPath, 0); err != nil { if err := pd.mounter.Unmount(globalPDPath); err != nil {
glog.V(2).Info("Error unmount dir ", globalPDPath, ": ", err) glog.V(2).Info("Error unmount dir ", globalPDPath, ": ", err)
return err return err
} }
@ -121,18 +121,21 @@ type awsSafeFormatAndMount struct {
} }
// uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk // uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk
func (mounter *awsSafeFormatAndMount) Mount(source string, target string, fstype string, flags uintptr, data string) error { func (mounter *awsSafeFormatAndMount) Mount(source string, target string, fstype string, options []string) error {
// Don't attempt to format if mounting as readonly. Go straight to mounting. // Don't attempt to format if mounting as readonly. Go straight to mounting.
if (flags & mount.FlagReadOnly) != 0 { // Don't attempt to format if mounting as readonly. Go straight to mounting.
return mounter.Interface.Mount(source, target, fstype, flags, data) for _, option := range options {
if option == "ro" {
return mounter.Interface.Mount(source, target, fstype, options)
}
} }
args := []string{} args := []string{}
// ext4 is the default for safe_format_and_mount // ext4 is the default for safe_format_and_mount
if len(fstype) > 0 && fstype != "ext4" { if len(fstype) > 0 && fstype != "ext4" {
args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype)) args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype))
} }
args = append(args, options...)
args = append(args, source, target) args = append(args, source, target)
// TODO: Accept other options here?
glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args) glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args)
cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...) cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...)
dataOut, err := cmd.CombinedOutput() dataOut, err := cmd.CombinedOutput()

View File

@ -64,7 +64,7 @@ func TestSafeFormatAndMount(t *testing.T) {
runner: &fake, runner: &fake,
} }
err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, 0, "") err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, nil)
if test.err == nil && err != nil { if test.err == nil && err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }

View File

@ -197,6 +197,7 @@ func (ed *emptyDir) setupTmpfs(dir string) error {
if isMnt && medium == mediumMemory { if isMnt && medium == mediumMemory {
return nil return nil
} }
// By default a tmpfs mount will receive a different SELinux context // By default a tmpfs mount will receive a different SELinux context
// from that of the Kubelet root directory which is not readable from // from that of the Kubelet root directory which is not readable from
// the SELinux context of a docker container. // the SELinux context of a docker container.
@ -206,15 +207,15 @@ func (ed *emptyDir) setupTmpfs(dir string) error {
// the container. // the container.
opts := ed.getTmpfsMountOptions() opts := ed.getTmpfsMountOptions()
glog.V(3).Infof("pod %v: mounting tmpfs for volume %v with opts %v", ed.podUID, ed.volName, opts) glog.V(3).Infof("pod %v: mounting tmpfs for volume %v with opts %v", ed.podUID, ed.volName, opts)
return ed.mounter.Mount("tmpfs", dir, "tmpfs", 0, opts) return ed.mounter.Mount("tmpfs", dir, "tmpfs", opts)
} }
func (ed *emptyDir) getTmpfsMountOptions() string { func (ed *emptyDir) getTmpfsMountOptions() []string {
if ed.rootContext == "" { if ed.rootContext == "" {
return "" return []string{""}
} }
return fmt.Sprintf("rootcontext=\"%v\"", ed.rootContext) return []string{fmt.Sprintf("rootcontext=\"%v\"", ed.rootContext)}
} }
func (ed *emptyDir) GetPath() string { func (ed *emptyDir) GetPath() string {
@ -261,7 +262,7 @@ func (ed *emptyDir) teardownTmpfs(dir string) error {
if ed.mounter == nil { if ed.mounter == nil {
return fmt.Errorf("memory storage requested, but mounter is nil") return fmt.Errorf("memory storage requested, but mounter is nil")
} }
if err := ed.mounter.Unmount(dir, 0); err != nil { if err := ed.mounter.Unmount(dir); err != nil {
return err return err
} }
if err := os.RemoveAll(dir); err != nil { if err := os.RemoveAll(dir); err != nil {

View File

@ -201,11 +201,6 @@ func (pd *gcePersistentDisk) SetUpAt(dir string) error {
return err return err
} }
flags := uintptr(0)
if pd.readOnly {
flags = mount.FlagReadOnly
}
if err := os.MkdirAll(dir, 0750); err != nil { if err := os.MkdirAll(dir, 0750); err != nil {
// TODO: we should really eject the attach/detach out into its own control loop. // TODO: we should really eject the attach/detach out into its own control loop.
detachDiskLogError(pd) detachDiskLogError(pd)
@ -213,7 +208,11 @@ func (pd *gcePersistentDisk) SetUpAt(dir string) error {
} }
// Perform a bind mount to the full path to allow duplicate mounts of the same PD. // Perform a bind mount to the full path to allow duplicate mounts of the same PD.
err = pd.mounter.Mount(globalPDPath, dir, "", mount.FlagBind|flags, "") options := []string{"bind"}
if pd.readOnly {
options = append(options, "ro")
}
err = pd.mounter.Mount(globalPDPath, dir, "", options)
if err != nil { if err != nil {
mountpoint, mntErr := pd.mounter.IsMountPoint(dir) mountpoint, mntErr := pd.mounter.IsMountPoint(dir)
if mntErr != nil { if mntErr != nil {
@ -221,7 +220,7 @@ func (pd *gcePersistentDisk) SetUpAt(dir string) error {
return err return err
} }
if mountpoint { if mountpoint {
if mntErr = pd.mounter.Unmount(dir, 0); mntErr != nil { if mntErr = pd.mounter.Unmount(dir); mntErr != nil {
glog.Errorf("Failed to unmount: %v", mntErr) glog.Errorf("Failed to unmount: %v", mntErr)
return err return err
} }
@ -279,7 +278,7 @@ func (pd *gcePersistentDisk) TearDownAt(dir string) error {
return err return err
} }
// Unmount the bind-mount inside this pod // Unmount the bind-mount inside this pod
if err := pd.mounter.Unmount(dir, 0); err != nil { if err := pd.mounter.Unmount(dir); err != nil {
return err return err
} }
// If len(refs) is 1, then all bind mounts have been removed, and the // If len(refs) is 1, then all bind mounts have been removed, and the

View File

@ -80,7 +80,7 @@ func (fake *fakePDManager) AttachAndMountDisk(pd *gcePersistentDisk, globalPDPat
fake.attachCalled = true fake.attachCalled = true
// Simulate the global mount so that the fakeMounter returns the // Simulate the global mount so that the fakeMounter returns the
// expected number of mounts for the attached disk. // expected number of mounts for the attached disk.
pd.mounter.Mount(globalPath, globalPath, pd.fsType, 0, "") pd.mounter.Mount(globalPath, globalPath, pd.fsType, nil)
return nil return nil
} }

View File

@ -39,10 +39,6 @@ func (util *GCEDiskUtil) AttachAndMountDisk(pd *gcePersistentDisk, globalPDPath
if err != nil { if err != nil {
return err return err
} }
flags := uintptr(0)
if pd.readOnly {
flags = mount.FlagReadOnly
}
if err := gce.(*gce_cloud.GCECloud).AttachDisk(pd.pdName, pd.readOnly); err != nil { if err := gce.(*gce_cloud.GCECloud).AttachDisk(pd.pdName, pd.readOnly); err != nil {
return err return err
} }
@ -79,8 +75,12 @@ func (util *GCEDiskUtil) AttachAndMountDisk(pd *gcePersistentDisk, globalPDPath
return err return err
} }
} }
options := []string{}
if pd.readOnly {
options = append(options, "ro")
}
if !mountpoint { if !mountpoint {
err = pd.diskMounter.Mount(devicePath, globalPDPath, pd.fsType, flags, "") err = pd.diskMounter.Mount(devicePath, globalPDPath, pd.fsType, options)
if err != nil { if err != nil {
os.Remove(globalPDPath) os.Remove(globalPDPath)
return err return err
@ -93,7 +93,7 @@ func (util *GCEDiskUtil) AttachAndMountDisk(pd *gcePersistentDisk, globalPDPath
func (util *GCEDiskUtil) DetachDisk(pd *gcePersistentDisk) error { func (util *GCEDiskUtil) DetachDisk(pd *gcePersistentDisk) error {
// Unmount the global PD mount, which should be the only one. // Unmount the global PD mount, which should be the only one.
globalPDPath := makeGlobalPDName(pd.plugin.host, pd.pdName) globalPDPath := makeGlobalPDName(pd.plugin.host, pd.pdName)
if err := pd.mounter.Unmount(globalPDPath, 0); err != nil { if err := pd.mounter.Unmount(globalPDPath); err != nil {
return err return err
} }
if err := os.Remove(globalPDPath); err != nil { if err := os.Remove(globalPDPath); err != nil {
@ -120,18 +120,20 @@ type gceSafeFormatAndMount struct {
} }
// uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk // uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk
func (mounter *gceSafeFormatAndMount) Mount(source string, target string, fstype string, flags uintptr, data string) error { func (mounter *gceSafeFormatAndMount) Mount(source string, target string, fstype string, options []string) error {
// Don't attempt to format if mounting as readonly. Go straight to mounting. // Don't attempt to format if mounting as readonly. Go straight to mounting.
if (flags & mount.FlagReadOnly) != 0 { for _, option := range options {
return mounter.Interface.Mount(source, target, fstype, flags, data) if option == "ro" {
return mounter.Interface.Mount(source, target, fstype, options)
}
} }
args := []string{} args := []string{}
// ext4 is the default for safe_format_and_mount // ext4 is the default for safe_format_and_mount
if len(fstype) > 0 && fstype != "ext4" { if len(fstype) > 0 && fstype != "ext4" {
args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype)) args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype))
} }
args = append(args, options...)
args = append(args, source, target) args = append(args, source, target)
// TODO: Accept other options here?
glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args) glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args)
cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...) cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...)
dataOut, err := cmd.CombinedOutput() dataOut, err := cmd.CombinedOutput()

View File

@ -64,7 +64,7 @@ func TestSafeFormatAndMount(t *testing.T) {
runner: &fake, runner: &fake,
} }
err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, 0, "") err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, nil)
if test.err == nil && err != nil { if test.err == nil && err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }

View File

@ -19,7 +19,6 @@ package glusterfs
import ( import (
"math/rand" "math/rand"
"os" "os"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/types"
@ -129,16 +128,15 @@ func (glusterfsVolume *glusterfs) SetUpAt(dir string) error {
if mountpoint { if mountpoint {
return nil return nil
} }
path := glusterfsVolume.path
os.MkdirAll(dir, 0750) os.MkdirAll(dir, 0750)
err = glusterfsVolume.execMount(glusterfsVolume.hosts, path, dir, glusterfsVolume.readonly) err = glusterfsVolume.setUpAtInternal(dir)
if err == nil { if err == nil {
return nil return nil
} }
// cleanup upon failure // Cleanup upon failure.
glusterfsVolume.cleanup(dir) glusterfsVolume.cleanup(dir)
// return error
return err return err
} }
@ -165,7 +163,7 @@ func (glusterfsVolume *glusterfs) cleanup(dir string) error {
return os.RemoveAll(dir) return os.RemoveAll(dir)
} }
if err := glusterfsVolume.mounter.Unmount(dir, 0); err != nil { if err := glusterfsVolume.mounter.Unmount(dir); err != nil {
glog.Errorf("Glusterfs: Unmounting failed: %v", err) glog.Errorf("Glusterfs: Unmounting failed: %v", err)
return err return err
} }
@ -183,30 +181,21 @@ func (glusterfsVolume *glusterfs) cleanup(dir string) error {
return nil return nil
} }
func (glusterfsVolume *glusterfs) execMount(hosts *api.Endpoints, path string, mountpoint string, readonly bool) error { func (glusterfsVolume *glusterfs) setUpAtInternal(dir string) error {
var errs error var errs error
var command exec.Cmd
var mountArgs []string
var opt []string
// build option array options := []string{}
if readonly == true { if glusterfsVolume.readonly {
opt = []string{"-o", "ro"} options = append(options, "ro")
} else {
opt = []string{"-o", "rw"}
} }
l := len(hosts.Subsets) l := len(glusterfsVolume.hosts.Subsets)
// avoid mount storm, pick a host randomly // Avoid mount storm, pick a host randomly.
start := rand.Int() % l start := rand.Int() % l
// iterate all hosts until mount succeeds. // Iterate all hosts until mount succeeds.
for i := start; i < start+l; i++ { for i := start; i < start+l; i++ {
arg := []string{"-t", "glusterfs", hosts.Subsets[i%l].Addresses[0].IP + ":" + path, mountpoint} hostIP := glusterfsVolume.hosts.Subsets[i%l].Addresses[0].IP
mountArgs = append(arg, opt...) errs = glusterfsVolume.mounter.Mount(hostIP+":"+glusterfsVolume.path, dir, "glusterfs", options)
glog.V(1).Infof("Glusterfs: mount cmd: mount %v", strings.Join(mountArgs, " "))
command = glusterfsVolume.exe.Command("mount", mountArgs...)
_, errs = command.CombinedOutput()
if errs == nil { if errs == nil {
return nil return nil
} }

View File

@ -55,11 +55,11 @@ func diskSetUp(manager diskManager, disk iscsiDisk, volPath string, mounter moun
return err return err
} }
// Perform a bind mount to the full path to allow duplicate mounts of the same disk. // Perform a bind mount to the full path to allow duplicate mounts of the same disk.
flags := uintptr(0) options := []string{"bind"}
if disk.readOnly { if disk.readOnly {
flags = mount.FlagReadOnly options = append(options, "ro")
} }
err = mounter.Mount(globalPDPath, volPath, "", mount.FlagBind|flags, "") err = mounter.Mount(globalPDPath, volPath, "", options)
if err != nil { if err != nil {
glog.Errorf("failed to bind mount:%s", globalPDPath) glog.Errorf("failed to bind mount:%s", globalPDPath)
return err return err
@ -83,8 +83,8 @@ func diskTearDown(manager diskManager, disk iscsiDisk, volPath string, mounter m
glog.Errorf("failed to get reference count %s", volPath) glog.Errorf("failed to get reference count %s", volPath)
return err return err
} }
if err := mounter.Unmount(volPath, 0); err != nil { if err := mounter.Unmount(volPath); err != nil {
glog.Errorf("failed to umount %s", volPath) glog.Errorf("failed to unmount %s", volPath)
return err return err
} }
// If len(refs) is 1, then all bind mounts have been removed, and the // If len(refs) is 1, then all bind mounts have been removed, and the

View File

@ -110,6 +110,11 @@ func (plugin *ISCSIPlugin) newCleanerInternal(volName string, podUID types.UID,
}, nil }, nil
} }
func (plugin *ISCSIPlugin) execCommand(command string, args []string) ([]byte, error) {
cmd := plugin.exe.Command(command, args...)
return cmd.CombinedOutput()
}
type iscsiDisk struct { type iscsiDisk struct {
volName string volName string
podUID types.UID podUID types.UID
@ -142,15 +147,13 @@ func (iscsi *iscsiDisk) SetUpAt(dir string) error {
return err return err
} }
globalPDPath := iscsi.manager.MakeGlobalPDName(*iscsi) globalPDPath := iscsi.manager.MakeGlobalPDName(*iscsi)
// make mountpoint rw/ro work as expected var options []string
//FIXME revisit pkg/util/mount and ensure rw/ro is implemented as expected
mode := "rw"
if iscsi.readOnly { if iscsi.readOnly {
mode = "ro" options = []string{"remount", "ro"}
} else {
options = []string{"remount", "rw"}
} }
iscsi.plugin.execCommand("mount", []string{"-o", "remount," + mode, globalPDPath, dir}) return iscsi.mounter.Mount(globalPDPath, dir, "", options)
return nil
} }
// Unmounts the bind mount, and detaches the disk only if the disk // Unmounts the bind mount, and detaches the disk only if the disk
@ -162,8 +165,3 @@ func (iscsi *iscsiDisk) TearDown() error {
func (iscsi *iscsiDisk) TearDownAt(dir string) error { func (iscsi *iscsiDisk) TearDownAt(dir string) error {
return diskTearDown(iscsi.manager, *iscsi, dir, iscsi.mounter) return diskTearDown(iscsi.manager, *iscsi, dir, iscsi.mounter)
} }
func (plugin *ISCSIPlugin) execCommand(command string, args []string) ([]byte, error) {
cmd := plugin.exe.Command(command, args...)
return cmd.CombinedOutput()
}

View File

@ -55,7 +55,7 @@ func (fake *fakeDiskManager) AttachDisk(disk iscsiDisk) error {
} }
// Simulate the global mount so that the fakeMounter returns the // Simulate the global mount so that the fakeMounter returns the
// expected number of mounts for the attached disk. // expected number of mounts for the attached disk.
disk.mounter.Mount(globalPath, globalPath, disk.fsType, 0, "") disk.mounter.Mount(globalPath, globalPath, disk.fsType, nil)
fake.attachCalled = true fake.attachCalled = true
return nil return nil

View File

@ -112,7 +112,7 @@ func (util *ISCSIUtil) AttachDisk(iscsi iscsiDisk) error {
return err return err
} }
err = iscsi.mounter.Mount(devicePath, globalPDPath, iscsi.fsType, uintptr(0), "") err = iscsi.mounter.Mount(devicePath, globalPDPath, iscsi.fsType, nil)
if err != nil { if err != nil {
glog.Errorf("iscsi: failed to mount iscsi volume %s [%s] to %s, error %v", devicePath, iscsi.fsType, globalPDPath, err) glog.Errorf("iscsi: failed to mount iscsi volume %s [%s] to %s, error %v", devicePath, iscsi.fsType, globalPDPath, err)
} }
@ -126,8 +126,8 @@ func (util *ISCSIUtil) DetachDisk(iscsi iscsiDisk, mntPath string) error {
glog.Errorf("iscsi detach disk: failed to get device from mnt: %s\nError: %v", mntPath, err) glog.Errorf("iscsi detach disk: failed to get device from mnt: %s\nError: %v", mntPath, err)
return err return err
} }
if err = iscsi.mounter.Unmount(mntPath, 0); err != nil { if err = iscsi.mounter.Unmount(mntPath); err != nil {
glog.Errorf("iscsi detach disk: failed to umount: %s\nError: %v", mntPath, err) glog.Errorf("iscsi detach disk: failed to unmount: %s\nError: %v", mntPath, err)
return err return err
} }
cnt-- cnt--

View File

@ -17,23 +17,25 @@ limitations under the License.
package nfs package nfs
import ( import (
"fmt"
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/types"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
"github.com/golang/glog" "github.com/golang/glog"
) )
// This is the primary entrypoint for volume plugins. // This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin { func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&nfsPlugin{nil, newNFSMounter()}} return []volume.VolumePlugin{&nfsPlugin{nil, mount.New()}}
} }
type nfsPlugin struct { type nfsPlugin struct {
host volume.VolumeHost host volume.VolumeHost
mounter nfsMountInterface mounter mount.Interface
} }
var _ volume.VolumePlugin = &nfsPlugin{} var _ volume.VolumePlugin = &nfsPlugin{}
@ -66,7 +68,7 @@ func (plugin *nfsPlugin) NewBuilder(spec *volume.Spec, podRef *api.ObjectReferen
return plugin.newBuilderInternal(spec, podRef, plugin.mounter) return plugin.newBuilderInternal(spec, podRef, plugin.mounter)
} }
func (plugin *nfsPlugin) newBuilderInternal(spec *volume.Spec, podRef *api.ObjectReference, mounter nfsMountInterface) (volume.Builder, error) { func (plugin *nfsPlugin) newBuilderInternal(spec *volume.Spec, podRef *api.ObjectReference, mounter mount.Interface) (volume.Builder, error) {
return &nfs{ return &nfs{
volName: spec.Name, volName: spec.Name,
server: spec.VolumeSource.NFS.Server, server: spec.VolumeSource.NFS.Server,
@ -82,7 +84,7 @@ func (plugin *nfsPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cl
return plugin.newCleanerInternal(volName, podUID, plugin.mounter) return plugin.newCleanerInternal(volName, podUID, plugin.mounter)
} }
func (plugin *nfsPlugin) newCleanerInternal(volName string, podUID types.UID, mounter nfsMountInterface) (volume.Cleaner, error) { func (plugin *nfsPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
return &nfs{ return &nfs{
volName: volName, volName: volName,
server: "", server: "",
@ -101,7 +103,7 @@ type nfs struct {
server string server string
exportPath string exportPath string
readOnly bool readOnly bool
mounter nfsMountInterface mounter mount.Interface
plugin *nfsPlugin plugin *nfsPlugin
} }
@ -119,9 +121,13 @@ func (nfsVolume *nfs) SetUpAt(dir string) error {
if mountpoint { if mountpoint {
return nil return nil
} }
exportDir := nfsVolume.exportPath
os.MkdirAll(dir, 0750) os.MkdirAll(dir, 0750)
err = nfsVolume.mounter.Mount(nfsVolume.server, exportDir, dir, nfsVolume.readOnly) source := fmt.Sprintf("%s:%s", nfsVolume.server, nfsVolume.exportPath)
options := []string{}
if nfsVolume.readOnly {
options = append(options, "ro")
}
err = nfsVolume.mounter.Mount(source, dir, "nfs", options)
if err != nil { if err != nil {
mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir) mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
if mntErr != nil { if mntErr != nil {

View File

@ -1,72 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 nfs
import (
"os/exec"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
"github.com/golang/glog"
)
type nfsMountInterface interface {
// Mount takes an NFS host ip or hostname, a source directory (the exported directory), a target directory where the source directory will be mounted, and a boolean readOnly
Mount(server string, source string, target string, readOnly bool) error
// Umount wraps syscall.Mount().
Unmount(target string) error
List() ([]mount.MountPoint, error)
IsMountPoint(dir string) (bool, error)
}
// newNFSMounter returns an nfsMountInterface for the current system.
func newNFSMounter() nfsMountInterface {
return &nfsMounter{}
}
type nfsMounter struct{}
func (mounter *nfsMounter) Mount(server string, exportDir string, mountDir string, readOnly bool) error {
mountOptions := "rw"
if readOnly {
mountOptions = "ro"
}
mountArgs := []string{"-t", "nfs", server + ":" + exportDir, mountDir, "-o", mountOptions}
command := exec.Command("mount", mountArgs...)
output, errs := command.CombinedOutput()
if errs != nil {
glog.Errorf("NFS mounting failed: %v\n\tMount args are: %v\n\texportDir is: %v\n\tmountDir is: %v\n\tserver is: %v\n\tmount output is: %v", errs, mountArgs, exportDir, mountDir, server, string(output))
return errs
}
return nil
}
func (mounter *nfsMounter) Unmount(target string) error {
unmounter := mount.New()
return unmounter.Unmount(target, 0)
}
func (mounter *nfsMounter) List() ([]mount.MountPoint, error) {
return nil, nil
}
func (mounter *nfsMounter) IsMountPoint(dir string) (bool, error) {
isMounter := mount.New()
return isMounter.IsMountPoint(dir)
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package nfs package nfs
import ( import (
"fmt"
"os" "os"
"testing" "testing"
@ -67,35 +66,6 @@ func contains(modes []api.AccessModeType, mode api.AccessModeType) bool {
return false return false
} }
type fakeNFSMounter struct {
FakeMounter mount.FakeMounter
}
func (fake *fakeNFSMounter) Mount(server string, source string, target string, readOnly bool) error {
flags := 0
if readOnly {
flags |= mount.FlagReadOnly
}
fake.FakeMounter.MountPoints = append(fake.FakeMounter.MountPoints, mount.MountPoint{Device: server, Path: target, Type: "nfs", Opts: nil, Freq: 0, Pass: 0})
return fake.FakeMounter.Mount(fmt.Sprintf("%s:%s", server, source), target, "nfs", 0, "")
}
func (fake *fakeNFSMounter) Unmount(target string) error {
fake.FakeMounter.MountPoints = []mount.MountPoint{}
return fake.FakeMounter.Unmount(target, 0)
}
func (fake *fakeNFSMounter) List() ([]mount.MountPoint, error) {
list, _ := fake.FakeMounter.List()
return list, nil
}
func (fake *fakeNFSMounter) IsMountPoint(dir string) (bool, error) {
list, _ := fake.FakeMounter.List()
isMount := len(list) > 0
return isMount, nil
}
func TestPlugin(t *testing.T) { func TestPlugin(t *testing.T) {
plugMgr := volume.VolumePluginMgr{} plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))
@ -107,7 +77,7 @@ func TestPlugin(t *testing.T) {
Name: "vol1", Name: "vol1",
VolumeSource: api.VolumeSource{NFS: &api.NFSVolumeSource{"localhost", "/tmp", false}}, VolumeSource: api.VolumeSource{NFS: &api.NFSVolumeSource{"localhost", "/tmp", false}},
} }
fake := &fakeNFSMounter{} fake := &mount.FakeMounter{}
builder, err := plug.(*nfsPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), &api.ObjectReference{UID: types.UID("poduid")}, fake) builder, err := plug.(*nfsPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), &api.ObjectReference{UID: types.UID("poduid")}, fake)
volumePath := builder.GetPath() volumePath := builder.GetPath()
if err != nil { if err != nil {
@ -133,14 +103,14 @@ func TestPlugin(t *testing.T) {
if builder.(*nfs).readOnly { if builder.(*nfs).readOnly {
t.Errorf("The volume source should not be read-only and it is.") t.Errorf("The volume source should not be read-only and it is.")
} }
if len(fake.FakeMounter.Log) != 1 { if len(fake.Log) != 1 {
t.Errorf("Mount was not called exactly one time. It was called %d times.", len(fake.FakeMounter.Log)) t.Errorf("Mount was not called exactly one time. It was called %d times.", len(fake.Log))
} else { } else {
if fake.FakeMounter.Log[0].Action != mount.FakeActionMount { if fake.Log[0].Action != mount.FakeActionMount {
t.Errorf("Unexpected mounter action: %#v", fake.FakeMounter.Log[0]) t.Errorf("Unexpected mounter action: %#v", fake.Log[0])
} }
} }
fake.FakeMounter.ResetLog() fake.ResetLog()
cleaner, err := plug.(*nfsPlugin).newCleanerInternal("vol1", types.UID("poduid"), fake) cleaner, err := plug.(*nfsPlugin).newCleanerInternal("vol1", types.UID("poduid"), fake)
if err != nil { if err != nil {
@ -157,13 +127,13 @@ func TestPlugin(t *testing.T) {
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
t.Errorf("SetUp() failed: %v", err) t.Errorf("SetUp() failed: %v", err)
} }
if len(fake.FakeMounter.Log) != 1 { if len(fake.Log) != 1 {
t.Errorf("Unmount was not called exactly one time. It was called %d times.", len(fake.FakeMounter.Log)) t.Errorf("Unmount was not called exactly one time. It was called %d times.", len(fake.Log))
} else { } else {
if fake.FakeMounter.Log[0].Action != mount.FakeActionUnmount { if fake.Log[0].Action != mount.FakeActionUnmount {
t.Errorf("Unexpected mounter action: %#v", fake.FakeMounter.Log[0]) t.Errorf("Unexpected mounter action: %#v", fake.Log[0])
} }
} }
fake.FakeMounter.ResetLog() fake.ResetLog()
} }