Promote volume plugins, prep for persistent vols
Move pkg/kubelet/volume/... to pkg/volume/... Some renames to make the soon-to-come persistent volumes work clearer.
This commit is contained in:
182
pkg/volume/nfs/nfs.go
Normal file
182
pkg/volume/nfs/nfs.go
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// This is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&nfsPlugin{nil, newNFSMounter()}}
|
||||
}
|
||||
|
||||
type nfsPlugin struct {
|
||||
host volume.VolumeHost
|
||||
mounter nfsMountInterface
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &nfsPlugin{}
|
||||
|
||||
const (
|
||||
nfsPluginName = "kubernetes.io/nfs"
|
||||
)
|
||||
|
||||
func (plugin *nfsPlugin) Init(host volume.VolumeHost) {
|
||||
plugin.host = host
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) Name() string {
|
||||
return nfsPluginName
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) CanSupport(spec *api.Volume) bool {
|
||||
if spec.VolumeSource.NFS != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) NewBuilder(spec *api.Volume, podRef *api.ObjectReference) (volume.Builder, error) {
|
||||
return plugin.newBuilderInternal(spec, podRef, plugin.mounter)
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) newBuilderInternal(spec *api.Volume, podRef *api.ObjectReference, mounter nfsMountInterface) (volume.Builder, error) {
|
||||
return &nfs{
|
||||
volName: spec.Name,
|
||||
server: spec.VolumeSource.NFS.Server,
|
||||
exportPath: spec.VolumeSource.NFS.Path,
|
||||
readOnly: spec.VolumeSource.NFS.ReadOnly,
|
||||
mounter: mounter,
|
||||
podRef: podRef,
|
||||
plugin: plugin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||
return plugin.newCleanerInternal(volName, podUID, plugin.mounter)
|
||||
}
|
||||
|
||||
func (plugin *nfsPlugin) newCleanerInternal(volName string, podUID types.UID, mounter nfsMountInterface) (volume.Cleaner, error) {
|
||||
return &nfs{
|
||||
volName: volName,
|
||||
server: "",
|
||||
exportPath: "",
|
||||
readOnly: false,
|
||||
mounter: mounter,
|
||||
podRef: &api.ObjectReference{UID: podUID},
|
||||
plugin: plugin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NFS volumes represent a bare host file or directory mount of an NFS export.
|
||||
type nfs struct {
|
||||
volName string
|
||||
podRef *api.ObjectReference
|
||||
server string
|
||||
exportPath string
|
||||
readOnly bool
|
||||
mounter nfsMountInterface
|
||||
plugin *nfsPlugin
|
||||
}
|
||||
|
||||
// SetUp attaches the disk and bind mounts to the volume path.
|
||||
func (nfsVolume *nfs) SetUp() error {
|
||||
return nfsVolume.SetUpAt(nfsVolume.GetPath())
|
||||
}
|
||||
|
||||
func (nfsVolume *nfs) SetUpAt(dir string) error {
|
||||
mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
|
||||
glog.V(4).Infof("NFS mount set up: %s %v %v", dir, mountpoint, err)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if mountpoint {
|
||||
return nil
|
||||
}
|
||||
exportDir := nfsVolume.exportPath
|
||||
os.MkdirAll(dir, 0750)
|
||||
err = nfsVolume.mounter.Mount(nfsVolume.server, exportDir, dir, nfsVolume.readOnly)
|
||||
if err != nil {
|
||||
mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsMountpoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if mountpoint {
|
||||
if mntErr = nfsVolume.mounter.Unmount(dir); mntErr != nil {
|
||||
glog.Errorf("Failed to unmount: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsMountpoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if mountpoint {
|
||||
// This is very odd, we don't expect it. We'll try again next sync loop.
|
||||
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(dir)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nfsVolume *nfs) GetPath() string {
|
||||
name := nfsPluginName
|
||||
return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.podRef.UID, volume.EscapePluginName(name), nfsVolume.volName)
|
||||
}
|
||||
|
||||
func (nfsVolume *nfs) TearDown() error {
|
||||
return nfsVolume.TearDownAt(nfsVolume.GetPath())
|
||||
}
|
||||
|
||||
func (nfsVolume *nfs) TearDownAt(dir string) error {
|
||||
mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
|
||||
if err != nil {
|
||||
glog.Errorf("Error checking IsMountPoint: %v", err)
|
||||
return err
|
||||
}
|
||||
if !mountpoint {
|
||||
return os.Remove(dir)
|
||||
}
|
||||
|
||||
if err := nfsVolume.mounter.Unmount(dir); err != nil {
|
||||
glog.Errorf("Unmounting failed: %v", err)
|
||||
return err
|
||||
}
|
||||
mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsMountpoint check failed: %v", mntErr)
|
||||
return mntErr
|
||||
}
|
||||
if !mountpoint {
|
||||
if err := os.Remove(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
71
pkg/volume/nfs/nfs_mount.go
Normal file
71
pkg/volume/nfs/nfs_mount.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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) {
|
||||
return mount.IsMountPoint(dir)
|
||||
}
|
147
pkg/volume/nfs/nfs_test.go
Normal file
147
pkg/volume/nfs/nfs_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("fake", nil, nil))
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/nfs")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if plug.Name() != "kubernetes.io/nfs" {
|
||||
t.Errorf("Wrong name: %s", plug.Name())
|
||||
}
|
||||
if !plug.CanSupport(&api.Volume{VolumeSource: api.VolumeSource{NFS: &api.NFSVolumeSource{}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
if plug.CanSupport(&api.Volume{VolumeSource: api.VolumeSource{}}) {
|
||||
t.Errorf("Expected 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) {
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/nfs")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &api.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: api.VolumeSource{NFS: &api.NFSVolumeSource{"localhost", "/tmp", false}},
|
||||
}
|
||||
fake := &fakeNFSMounter{}
|
||||
builder, err := plug.(*nfsPlugin).newBuilderInternal(spec, &api.ObjectReference{UID: types.UID("poduid")}, fake)
|
||||
volumePath := builder.GetPath()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
if builder == nil {
|
||||
t.Errorf("Got a nil Builder: %v")
|
||||
}
|
||||
path := builder.GetPath()
|
||||
if path != "/tmp/fake/pods/poduid/volumes/kubernetes.io~nfs/vol1" {
|
||||
t.Errorf("Got unexpected path: %s", path)
|
||||
}
|
||||
if err := builder.SetUp(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volumePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
if builder.(*nfs).readOnly {
|
||||
t.Errorf("The volume source should not be read-only and it is.")
|
||||
}
|
||||
if len(fake.FakeMounter.Log) != 1 {
|
||||
t.Errorf("Mount was not called exactly one time. It was called %d times.", len(fake.FakeMounter.Log))
|
||||
} else {
|
||||
if fake.FakeMounter.Log[0].Action != mount.FakeActionMount {
|
||||
t.Errorf("Unexpected mounter action: %#v", fake.FakeMounter.Log[0])
|
||||
}
|
||||
}
|
||||
fake.FakeMounter.ResetLog()
|
||||
|
||||
cleaner, err := plug.(*nfsPlugin).newCleanerInternal("vol1", types.UID("poduid"), fake)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Cleaner: %v", err)
|
||||
}
|
||||
if cleaner == nil {
|
||||
t.Errorf("Got a nil Cleaner: %v")
|
||||
}
|
||||
if err := cleaner.TearDown(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volumePath); err == nil {
|
||||
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
if len(fake.FakeMounter.Log) != 1 {
|
||||
t.Errorf("Unmount was not called exactly one time. It was called %d times.", len(fake.FakeMounter.Log))
|
||||
} else {
|
||||
if fake.FakeMounter.Log[0].Action != mount.FakeActionUnmount {
|
||||
t.Errorf("Unexpected mounter action: %#v", fake.FakeMounter.Log[0])
|
||||
}
|
||||
}
|
||||
|
||||
fake.FakeMounter.ResetLog()
|
||||
}
|
Reference in New Issue
Block a user