mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
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-server
This commit is contained in:
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/gce_pd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/git_repo"
|
"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/host_path"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/nfs"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/secret"
|
"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, git_repo.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins()...)
|
||||||
|
|
||||||
return allPlugins
|
return allPlugins
|
||||||
}
|
}
|
||||||
|
21
examples/nfs/test.yaml
Normal file
21
examples/nfs/test.yaml
Normal file
@ -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
|
9
examples/storage/persistentvolume-nfs-example.yaml
Normal file
9
examples/storage/persistentvolume-nfs-example.yaml
Normal file
@ -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) {
|
func(vs *api.VolumeSource, c fuzz.Continue) {
|
||||||
// Exactly one of the fields should be set.
|
// Exactly one of the fields should be set.
|
||||||
//FIXME: the fuzz can still end up nil. What if fuzz allowed me to say that?
|
//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) {
|
func(d *api.DNSPolicy, c fuzz.Continue) {
|
||||||
policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault}
|
policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault}
|
||||||
|
@ -180,6 +180,8 @@ type VolumeSource struct {
|
|||||||
GitRepo *GitRepoVolumeSource `json:"gitRepo"`
|
GitRepo *GitRepoVolumeSource `json:"gitRepo"`
|
||||||
// Secret represents a secret that should populate this volume.
|
// Secret represents a secret that should populate this volume.
|
||||||
Secret *SecretVolumeSource `json:"secret"`
|
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.
|
// HostPathVolumeSource represents a host directory mapped into a pod.
|
||||||
@ -256,6 +258,19 @@ type SecretVolumeSource struct {
|
|||||||
Target ObjectReference `json:"target"`
|
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
|
// ContainerPort represents a network port in a single container
|
||||||
type ContainerPort struct {
|
type ContainerPort struct {
|
||||||
// Optional: If specified, this must be a DNS_LABEL. Each named port
|
// 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 {
|
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
|
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 {
|
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -105,6 +105,8 @@ type VolumeSource struct {
|
|||||||
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||||
// Secret represents a secret to populate the volume with
|
// Secret represents a secret to populate the volume with
|
||||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate 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.
|
// 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"`
|
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
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
// the Data field must be less than MaxSecretSize bytes.
|
// the Data field must be less than MaxSecretSize bytes.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
|
@ -982,6 +982,9 @@ func init() {
|
|||||||
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
|
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 {
|
if err := s.Convert(&in.Secret, &out.Secret, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -78,6 +78,8 @@ type VolumeSource struct {
|
|||||||
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||||
// Secret is a secret to populate the volume with
|
// Secret is a secret to populate the volume with
|
||||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate 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.
|
// 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"`
|
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
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
// the Data field must be less than MaxSecretSize bytes.
|
// 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"`
|
GitRepo *GitRepoVolumeSource `json:"gitRepo" description:"git repository at a particular revision"`
|
||||||
// Secret represents a secret that should populate this volume.
|
// Secret represents a secret that should populate this volume.
|
||||||
Secret *SecretVolumeSource `json:"secret" description:"secret to populate 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.
|
// 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"`
|
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.
|
// ContainerPort represents a network port in a single container.
|
||||||
type ContainerPort struct {
|
type ContainerPort struct {
|
||||||
// Optional: If specified, this must be a DNS_LABEL. Each named port
|
// Optional: If specified, this must be a DNS_LABEL. Each named port
|
||||||
|
@ -18,6 +18,7 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
@ -290,6 +291,10 @@ func validateSource(source *api.VolumeSource) errs.ValidationErrorList {
|
|||||||
numVolumes++
|
numVolumes++
|
||||||
allErrs = append(allErrs, validateSecretVolumeSource(source.Secret).Prefix("secret")...)
|
allErrs = append(allErrs, validateSecretVolumeSource(source.Secret).Prefix("secret")...)
|
||||||
}
|
}
|
||||||
|
if source.NFS != nil {
|
||||||
|
numVolumes++
|
||||||
|
allErrs = append(allErrs, validateNFS(source.NFS).Prefix("nfs")...)
|
||||||
|
}
|
||||||
if numVolumes != 1 {
|
if numVolumes != 1 {
|
||||||
allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required"))
|
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
|
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))
|
var supportedPortProtocols = util.NewStringSet(string(api.ProtocolTCP), string(api.ProtocolUDP))
|
||||||
|
|
||||||
func validatePorts(ports []api.ContainerPort) errs.ValidationErrorList {
|
func validatePorts(ports []api.ContainerPort) errs.ValidationErrorList {
|
||||||
|
182
pkg/kubelet/volume/nfs/nfs.go
Normal file
182
pkg/kubelet/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/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
|
||||||
|
}
|
71
pkg/kubelet/volume/nfs/nfs_mount.go
Normal file
71
pkg/kubelet/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"
|
||||||
|
"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)
|
||||||
|
}
|
147
pkg/kubelet/volume/nfs/nfs_test.go
Normal file
147
pkg/kubelet/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/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
Block a user