mirror of https://github.com/k3s-io/k3s
NFSMount storage plugin for kubelet.
* If you want to test this out when an actual NFS export a good place to start is by running the NFS server in a container: docker run -d --name nfs --privileged cpuguy83/nfs-server /tmp More detail can be found here: https://github.com/cpuguy83/docker-nfs-serverpull/6/head
parent
d845d49dc6
commit
1a45e37d17
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/gce_pd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/git_repo"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/host_path"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/nfs"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/secret"
|
||||
)
|
||||
|
||||
|
@ -41,6 +42,7 @@ func ProbeVolumePlugins() []volume.Plugin {
|
|||
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins()...)
|
||||
|
||||
return allPlugins
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
apiVersion: v1beta1
|
||||
desiredState:
|
||||
manifest:
|
||||
containers:
|
||||
- name: testpd
|
||||
image: dockerfile/nginx
|
||||
volumeMounts:
|
||||
# name must match the volume name below
|
||||
- name: nfs
|
||||
mountPath: "/var/www/html/mount-test"
|
||||
id: nfspd
|
||||
version: v1beta1
|
||||
volumes:
|
||||
- name: nfs
|
||||
source:
|
||||
nfs:
|
||||
server: "172.17.0.2"
|
||||
path: "/tmp"
|
||||
readOnly: false
|
||||
id: nfspd
|
||||
kind: Pod
|
|
@ -0,0 +1,9 @@
|
|||
id: pv0003
|
||||
kind: PersistentVolume
|
||||
apiVersion: v1beta1
|
||||
spec:
|
||||
source:
|
||||
nfsMount:
|
||||
server: "172.17.0.2"
|
||||
path: "/tmp"
|
||||
readOnly: false
|
|
@ -167,7 +167,7 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
|
|||
func(vs *api.VolumeSource, c fuzz.Continue) {
|
||||
// Exactly one of the fields should be set.
|
||||
//FIXME: the fuzz can still end up nil. What if fuzz allowed me to say that?
|
||||
fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo, &vs.Secret)
|
||||
fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo, &vs.Secret, &vs.NFS)
|
||||
},
|
||||
func(d *api.DNSPolicy, c fuzz.Continue) {
|
||||
policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault}
|
||||
|
|
|
@ -180,6 +180,8 @@ type VolumeSource struct {
|
|||
GitRepo *GitRepoVolumeSource `json:"gitRepo"`
|
||||
// Secret represents a secret that should populate this volume.
|
||||
Secret *SecretVolumeSource `json:"secret"`
|
||||
// NFS represents an NFS mount on the host that shares a pod's lifetime
|
||||
NFS *NFSVolumeSource `json:"nfs"`
|
||||
}
|
||||
|
||||
// HostPathVolumeSource represents a host directory mapped into a pod.
|
||||
|
@ -256,6 +258,19 @@ type SecretVolumeSource struct {
|
|||
Target ObjectReference `json:"target"`
|
||||
}
|
||||
|
||||
// NFSVolumeSource represents an NFS Mount that lasts the lifetime of a pod
|
||||
type NFSVolumeSource struct {
|
||||
// Server is the hostname or IP address of the NFS server
|
||||
Server string `json:"server"`
|
||||
|
||||
// Path is the exported NFS share
|
||||
Path string `json:"path"`
|
||||
|
||||
// Optional: Defaults to false (read/write). ReadOnly here will force
|
||||
// the NFS export to be mounted with read-only permissions
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerPort represents a network port in a single container
|
||||
type ContainerPort struct {
|
||||
// Optional: If specified, this must be a DNS_LABEL. Each named port
|
||||
|
|
|
@ -1054,6 +1054,9 @@ func init() {
|
|||
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
|
||||
|
@ -1072,6 +1075,9 @@ func init() {
|
|||
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
|
|
|
@ -105,6 +105,8 @@ type VolumeSource struct {
|
|||
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||
// Secret represents a secret to populate the volume with
|
||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate volume with"`
|
||||
// NFS represents an NFS mount on the host that shares a pod's lifetime
|
||||
NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine "`
|
||||
}
|
||||
|
||||
// HostPathVolumeSource represents bare host directory volume.
|
||||
|
@ -1154,6 +1156,19 @@ type ResourceQuotaList struct {
|
|||
Items []ResourceQuota `json:"items" description:"items is a list of ResourceQuota objects"`
|
||||
}
|
||||
|
||||
// NFSVolumeSource represents an NFS Mount that lasts the lifetime of a pod
|
||||
type NFSVolumeSource struct {
|
||||
// Server is the hostname or IP address of the NFS server
|
||||
Server string `json:"server" description:"the hostname or IP address of the NFS server"`
|
||||
|
||||
// Path is the exported NFS share
|
||||
Path string `json:"path" description:"the path that is exported by the NFS server"`
|
||||
|
||||
// Optional: Defaults to false (read/write). ReadOnly here will force
|
||||
// the NFS export to be mounted as read-only permissions
|
||||
ReadOnly bool `json:"readOnly,omitempty" description:"forces the NFS export to be mounted with read-only permissions"`
|
||||
}
|
||||
|
||||
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||
// the Data field must be less than MaxSecretSize bytes.
|
||||
type Secret struct {
|
||||
|
|
|
@ -982,6 +982,9 @@ func init() {
|
|||
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
|
||||
|
@ -1000,6 +1003,9 @@ func init() {
|
|||
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
|
|
|
@ -78,6 +78,8 @@ type VolumeSource struct {
|
|||
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||
// Secret is a secret to populate the volume with
|
||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate volume"`
|
||||
// NFS represents an NFS mount on the host that shares a pod's lifetime
|
||||
NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"`
|
||||
}
|
||||
|
||||
// HostPathVolumeSource represents bare host directory volume.
|
||||
|
@ -1215,6 +1217,19 @@ type ResourceQuotaList struct {
|
|||
Items []ResourceQuota `json:"items" description:"items is a list of ResourceQuota objects"`
|
||||
}
|
||||
|
||||
// NFSVolumeSource represents an NFS mount that lasts the lifetime of a pod
|
||||
type NFSVolumeSource struct {
|
||||
// Server is the hostname or IP address of the NFS server
|
||||
Server string `json:"server" description:"the hostname or IP address of the NFS server"`
|
||||
|
||||
// Path is the exported NFS share
|
||||
Path string `json:"path" description:"the path that is exported by the NFS server"`
|
||||
|
||||
// Optional: Defaults to false (read/write). ReadOnly here will force
|
||||
// the NFS export to be mounted with read-only permissions
|
||||
ReadOnly bool `json:"readOnly,omitempty" description:"forces the NFS export to be mounted with read-only permissions"`
|
||||
}
|
||||
|
||||
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||
// the Data field must be less than MaxSecretSize bytes.
|
||||
//
|
||||
|
|
|
@ -199,6 +199,8 @@ type VolumeSource struct {
|
|||
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||
// Secret represents a secret that should populate this volume.
|
||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate volume"`
|
||||
// NFS represents an NFS mount on the host that shares a pod's lifetime
|
||||
NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"`
|
||||
}
|
||||
|
||||
// HostPathVolumeSource represents bare host directory volume.
|
||||
|
@ -266,6 +268,19 @@ type SecretVolumeSource struct {
|
|||
Target ObjectReference `json:"target" description:"target is a reference to a secret"`
|
||||
}
|
||||
|
||||
// NFSVolumeSource represents an NFS mount that lasts the lifetime of a pod
|
||||
type NFSVolumeSource struct {
|
||||
// Server is the hostname or IP address of the NFS server
|
||||
Server string `json:"server" description:"the hostname or IP address of the NFS server"`
|
||||
|
||||
// Path is the exported NFS share
|
||||
Path string `json:"path" description:"the path that is exported by the NFS server"`
|
||||
|
||||
// Optional: Defaults to false (read/write). ReadOnly here will force
|
||||
// the NFS export to be mounted with read-only permissions
|
||||
ReadOnly bool `json:"readOnly,omitempty" description:"forces the NFS export to be mounted with read-only permissions"`
|
||||
}
|
||||
|
||||
// ContainerPort represents a network port in a single container.
|
||||
type ContainerPort struct {
|
||||
// Optional: If specified, this must be a DNS_LABEL. Each named port
|
||||
|
|
|
@ -18,6 +18,7 @@ package validation
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
|
@ -290,6 +291,10 @@ func validateSource(source *api.VolumeSource) errs.ValidationErrorList {
|
|||
numVolumes++
|
||||
allErrs = append(allErrs, validateSecretVolumeSource(source.Secret).Prefix("secret")...)
|
||||
}
|
||||
if source.NFS != nil {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateNFS(source.NFS).Prefix("nfs")...)
|
||||
}
|
||||
if numVolumes != 1 {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required"))
|
||||
}
|
||||
|
@ -340,6 +345,20 @@ func validateSecretVolumeSource(secretSource *api.SecretVolumeSource) errs.Valid
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateNFS(nfs *api.NFSVolumeSource) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
if nfs.Server == "" {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("server"))
|
||||
}
|
||||
if nfs.Path == "" {
|
||||
allErrs = append(allErrs, errs.NewFieldRequired("path"))
|
||||
}
|
||||
if !path.IsAbs(nfs.Path) {
|
||||
allErrs = append(allErrs, errs.NewFieldInvalid("path", nfs.Path, "must be an absolute path"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var supportedPortProtocols = util.NewStringSet(string(api.ProtocolTCP), string(api.ProtocolUDP))
|
||||
|
||||
func validatePorts(ports []api.ContainerPort) errs.ValidationErrorList {
|
||||
|
|
|
@ -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/kubelet/volume"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// This is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.Plugin {
|
||||
return []volume.Plugin{&nfsPlugin{nil, newNFSMounter()}}
|
||||
}
|
||||
|
||||
type nfsPlugin struct {
|
||||
host volume.Host
|
||||
mounter nfsMountInterface
|
||||
}
|
||||
|
||||
var _ volume.Plugin = &nfsPlugin{}
|
||||
|
||||
const (
|
||||
nfsPluginName = "kubernetes.io/nfs"
|
||||
)
|
||||
|
||||
func (plugin *nfsPlugin) Init(host volume.Host) {
|
||||
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
|
||||
}
|
|
@ -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"
|
||||
"syscall"
|
||||
|
||||
"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 {
|
||||
return syscall.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)
|
||||
}
|
|
@ -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/kubelet/volume"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
plugMgr := volume.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeHost("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.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeHost("/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()
|
||||
}
|
Loading…
Reference in New Issue