Commit 3bc342cf authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #41906 from gnufied/implement-mount-options

Automatic merge from submit-queue Implement support for mount options in PVs **What this PR does / why we need it**: This PR implements support for mount options in PersistentVolume via `volume.beta.kubernetes.io/mount-options` annotation. **Which issue this PR fixes** Fixes https://github.com/kubernetes/features/issues/168 **Release note**: ``` Enable additional, custom mount options to be passed to PersistentVolume objects via volume.beta.kubernetes.io/mount-options annotation. ```
parents c713ef43 2d3008fc
...@@ -15,6 +15,7 @@ go_library( ...@@ -15,6 +15,7 @@ go_library(
"events.go", "events.go",
"schema.go", "schema.go",
"validation.go", "validation.go",
"volume_plugins.go",
], ],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
...@@ -27,6 +28,30 @@ go_library( ...@@ -27,6 +28,30 @@ go_library(
"//pkg/capabilities:go_default_library", "//pkg/capabilities:go_default_library",
"//pkg/features:go_default_library", "//pkg/features:go_default_library",
"//pkg/security/apparmor:go_default_library", "//pkg/security/apparmor:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/aws_ebs:go_default_library",
"//pkg/volume/azure_dd:go_default_library",
"//pkg/volume/azure_file:go_default_library",
"//pkg/volume/cephfs:go_default_library",
"//pkg/volume/cinder:go_default_library",
"//pkg/volume/configmap:go_default_library",
"//pkg/volume/downwardapi:go_default_library",
"//pkg/volume/empty_dir:go_default_library",
"//pkg/volume/fc:go_default_library",
"//pkg/volume/flexvolume:go_default_library",
"//pkg/volume/flocker:go_default_library",
"//pkg/volume/gce_pd:go_default_library",
"//pkg/volume/git_repo:go_default_library",
"//pkg/volume/glusterfs:go_default_library",
"//pkg/volume/host_path:go_default_library",
"//pkg/volume/iscsi:go_default_library",
"//pkg/volume/nfs:go_default_library",
"//pkg/volume/photon_pd:go_default_library",
"//pkg/volume/projected:go_default_library",
"//pkg/volume/quobyte:go_default_library",
"//pkg/volume/rbd:go_default_library",
"//pkg/volume/secret:go_default_library",
"//pkg/volume/vsphere_volume:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger", "//vendor:github.com/emicklei/go-restful/swagger",
"//vendor:github.com/exponent-io/jsonpath", "//vendor:github.com/exponent-io/jsonpath",
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
...@@ -77,6 +102,7 @@ go_test( ...@@ -77,6 +102,7 @@ go_test(
"//pkg/apis/storage/util:go_default_library", "//pkg/apis/storage/util:go_default_library",
"//pkg/capabilities:go_default_library", "//pkg/capabilities:go_default_library",
"//pkg/security/apparmor:go_default_library", "//pkg/security/apparmor:go_default_library",
"//pkg/volume:go_default_library",
"//vendor:github.com/ghodss/yaml", "//vendor:github.com/ghodss/yaml",
"//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/api/testing", "//vendor:k8s.io/apimachinery/pkg/api/testing",
......
...@@ -48,6 +48,7 @@ import ( ...@@ -48,6 +48,7 @@ import (
"k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/volume"
) )
// TODO: delete this global variable when we enable the validation of common // TODO: delete this global variable when we enable the validation of common
...@@ -64,6 +65,11 @@ var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), bo ...@@ -64,6 +65,11 @@ var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), bo
// BannedOwners is a black list of object that are not allowed to be owners. // BannedOwners is a black list of object that are not allowed to be owners.
var BannedOwners = genericvalidation.BannedOwners var BannedOwners = genericvalidation.BannedOwners
var volumePlugins []volume.VolumePlugin
func init() {
volumePlugins = probeVolumePlugins()
}
// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue // ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue
func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
...@@ -1032,6 +1038,20 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { ...@@ -1032,6 +1038,20 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
} }
} }
volumePlugin := findPluginBySpec(volumePlugins, pv)
mountOptions := volume.MountOptionFromApiPV(pv)
metaField := field.NewPath("metadata")
if volumePlugin == nil && len(mountOptions) > 0 {
allErrs = append(allErrs, field.Forbidden(metaField.Child("annotations", volume.MountOptionAnnotation), "may not specify mount options for this volume type"))
}
if volumePlugin != nil {
if !volumePlugin.SupportsMountOption() && len(mountOptions) > 0 {
allErrs = append(allErrs, field.Forbidden(metaField.Child("annotations", volume.MountOptionAnnotation), "may not specify mount options for this volume type"))
}
}
numVolumes := 0 numVolumes := 0
if pv.Spec.HostPath != nil { if pv.Spec.HostPath != nil {
if numVolumes > 0 { if numVolumes > 0 {
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
storageutil "k8s.io/kubernetes/pkg/apis/storage/util" storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
"k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/volume"
) )
const ( const (
...@@ -205,6 +206,30 @@ func TestValidatePersistentVolumes(t *testing.T) { ...@@ -205,6 +206,30 @@ func TestValidatePersistentVolumes(t *testing.T) {
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle,
}), }),
}, },
"volume with valid mount option for nfs": {
isExpectedFailure: false,
volume: testVolumeWithMountOption("good-nfs-mount-volume", "", "ro,nfsvers=3", api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
NFS: &api.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
},
}),
},
"volume with mount option for host path": {
isExpectedFailure: true,
volume: testVolumeWithMountOption("bad-hostpath-mount-volume", "", "ro,nfsvers=3", api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/a/.."},
},
}),
},
} }
for name, scenario := range scenarios { for name, scenario := range scenarios {
...@@ -241,6 +266,25 @@ func testVolumeClaimStorageClass(name string, namespace string, annval string, s ...@@ -241,6 +266,25 @@ func testVolumeClaimStorageClass(name string, namespace string, annval string, s
} }
} }
func testVolumeWithMountOption(name string, namespace string, mountOptions string, spec api.PersistentVolumeSpec) *api.PersistentVolume {
annotations := map[string]string{
volume.MountOptionAnnotation: mountOptions,
}
objMeta := metav1.ObjectMeta{
Name: name,
Annotations: annotations,
}
if namespace != "" {
objMeta.Namespace = namespace
}
return &api.PersistentVolume{
ObjectMeta: objMeta,
Spec: spec,
}
}
func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim {
annotations := map[string]string{ annotations := map[string]string{
ann: annval, ann: annval,
......
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/aws_ebs"
"k8s.io/kubernetes/pkg/volume/azure_dd"
"k8s.io/kubernetes/pkg/volume/azure_file"
"k8s.io/kubernetes/pkg/volume/cephfs"
"k8s.io/kubernetes/pkg/volume/cinder"
"k8s.io/kubernetes/pkg/volume/configmap"
"k8s.io/kubernetes/pkg/volume/downwardapi"
"k8s.io/kubernetes/pkg/volume/empty_dir"
"k8s.io/kubernetes/pkg/volume/fc"
"k8s.io/kubernetes/pkg/volume/flexvolume"
"k8s.io/kubernetes/pkg/volume/flocker"
"k8s.io/kubernetes/pkg/volume/gce_pd"
"k8s.io/kubernetes/pkg/volume/git_repo"
"k8s.io/kubernetes/pkg/volume/glusterfs"
"k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/projected"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/secret"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
)
func probeVolumePlugins() []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
// list of volume plugins to probe for
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, empty_dir.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins(volume.VolumeConfig{})...)
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(volume.VolumeConfig{})...)
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, downwardapi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins("")...)
allPlugins = append(allPlugins, azure_file.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
return allPlugins
}
func findPluginBySpec(volumePlugins []volume.VolumePlugin, pv *api.PersistentVolume) volume.VolumePlugin {
matches := []volume.VolumePlugin{}
v1Pv := &v1.PersistentVolume{}
err := v1.Convert_api_PersistentVolume_To_v1_PersistentVolume(pv, v1Pv, nil)
if err != nil {
glog.Errorf("Error converting to v1.PersistentVolume: %v", err)
return nil
}
volumeSpec := &volume.Spec{PersistentVolume: v1Pv}
for _, plugin := range volumePlugins {
if plugin.CanSupport(volumeSpec) {
matches = append(matches, plugin)
}
}
if len(matches) == 0 {
glog.V(5).Infof("No matching plugin found for : %s", pv.Name)
return nil
}
if len(matches) > 1 {
glog.V(3).Infof("multiple volume plugins matched for : %s ", pv.Name)
return nil
}
return matches[0]
}
...@@ -1130,6 +1130,10 @@ func (plugin *mockVolumePlugin) RequiresRemount() bool { ...@@ -1130,6 +1130,10 @@ func (plugin *mockVolumePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *mockVolumePlugin) SupportsMountOption() bool {
return false
}
func (plugin *mockVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*vol.Spec, error) { func (plugin *mockVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*vol.Spec, error) {
return nil, nil return nil, nil
} }
......
...@@ -65,6 +65,7 @@ const ( ...@@ -65,6 +65,7 @@ const (
ImageGCFailed = "ImageGCFailed" ImageGCFailed = "ImageGCFailed"
FailedNodeAllocatableEnforcement = "FailedNodeAllocatableEnforcement" FailedNodeAllocatableEnforcement = "FailedNodeAllocatableEnforcement"
SuccessfulNodeAllocatableEnforcement = "NodeAllocatableEnforced" SuccessfulNodeAllocatableEnforcement = "NodeAllocatableEnforced"
UnsupportedMountOption = "UnsupportedMountOption"
// Image manager event reason list // Image manager event reason list
InvalidDiskCapacity = "InvalidDiskCapacity" InvalidDiskCapacity = "InvalidDiskCapacity"
......
...@@ -24,6 +24,7 @@ go_library( ...@@ -24,6 +24,7 @@ go_library(
], ],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider:go_default_library",
...@@ -58,6 +59,7 @@ go_test( ...@@ -58,6 +59,7 @@ go_test(
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/util/slice:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
......
...@@ -193,7 +193,8 @@ func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, dev ...@@ -193,7 +193,8 @@ func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, dev
} }
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -87,6 +87,10 @@ func (plugin *awsElasticBlockStorePlugin) RequiresRemount() bool { ...@@ -87,6 +87,10 @@ func (plugin *awsElasticBlockStorePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *awsElasticBlockStorePlugin) SupportsMountOption() bool {
return true
}
func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -218,7 +218,8 @@ func (attacher *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath str ...@@ -218,7 +218,8 @@ func (attacher *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath str
} }
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, *volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, *volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -103,6 +103,10 @@ func (plugin *azureDataDiskPlugin) RequiresRemount() bool { ...@@ -103,6 +103,10 @@ func (plugin *azureDataDiskPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *azureDataDiskPlugin) SupportsMountOption() bool {
return true
}
func (plugin *azureDataDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *azureDataDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -79,6 +79,10 @@ func (plugin *azureFilePlugin) RequiresRemount() bool { ...@@ -79,6 +79,10 @@ func (plugin *azureFilePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *azureFilePlugin) SupportsMountOption() bool {
return true
}
func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -105,10 +109,11 @@ func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod ...@@ -105,10 +109,11 @@ func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod
plugin: plugin, plugin: plugin,
MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)), MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
}, },
util: util, util: util,
secretName: source.SecretName, secretName: source.SecretName,
shareName: source.ShareName, shareName: source.ShareName,
readOnly: readOnly, readOnly: readOnly,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -154,10 +159,11 @@ func (azureFileVolume *azureFile) GetPath() string { ...@@ -154,10 +159,11 @@ func (azureFileVolume *azureFile) GetPath() string {
type azureFileMounter struct { type azureFileMounter struct {
*azureFile *azureFile
util azureUtil util azureUtil
secretName string secretName string
shareName string shareName string
readOnly bool readOnly bool
mountOptions []string
} }
var _ volume.Mounter = &azureFileMounter{} var _ volume.Mounter = &azureFileMounter{}
...@@ -202,7 +208,8 @@ func (b *azureFileMounter) SetUpAt(dir string, fsGroup *int64) error { ...@@ -202,7 +208,8 @@ func (b *azureFileMounter) SetUpAt(dir string, fsGroup *int64) error {
if b.readOnly { if b.readOnly {
options = append(options, "ro") options = append(options, "ro")
} }
err = b.mounter.Mount(source, dir, "cifs", options) mountOptions := volume.JoinMountOptions(b.mountOptions, options)
err = b.mounter.Mount(source, dir, "cifs", mountOptions)
if err != nil { if err != nil {
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil { if mntErr != nil {
......
...@@ -72,6 +72,10 @@ func (plugin *cephfsPlugin) RequiresRemount() bool { ...@@ -72,6 +72,10 @@ func (plugin *cephfsPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *cephfsPlugin) SupportsMountOption() bool {
return true
}
func (plugin *cephfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *cephfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -129,16 +133,18 @@ func (plugin *cephfsPlugin) newMounterInternal(spec *volume.Spec, podUID types.U ...@@ -129,16 +133,18 @@ func (plugin *cephfsPlugin) newMounterInternal(spec *volume.Spec, podUID types.U
return &cephfsMounter{ return &cephfsMounter{
cephfs: &cephfs{ cephfs: &cephfs{
podUID: podUID, podUID: podUID,
volName: spec.Name(), volName: spec.Name(),
mon: cephvs.Monitors, mon: cephvs.Monitors,
path: path, path: path,
secret: secret, secret: secret,
id: id, id: id,
secret_file: secret_file, secret_file: secret_file,
readonly: cephvs.ReadOnly, readonly: cephvs.ReadOnly,
mounter: mounter, mounter: mounter,
plugin: plugin}, plugin: plugin,
mountOptions: volume.MountOptionFromSpec(spec),
},
}, nil }, nil
} }
...@@ -182,6 +188,7 @@ type cephfs struct { ...@@ -182,6 +188,7 @@ type cephfs struct {
mounter mount.Interface mounter mount.Interface
plugin *cephfsPlugin plugin *cephfsPlugin
volume.MetricsNil volume.MetricsNil
mountOptions []string
} }
type cephfsMounter struct { type cephfsMounter struct {
...@@ -282,7 +289,8 @@ func (cephfsVolume *cephfs) execMount(mountpoint string) error { ...@@ -282,7 +289,8 @@ func (cephfsVolume *cephfs) execMount(mountpoint string) error {
} }
src += hosts[i] + ":" + cephfsVolume.path src += hosts[i] + ":" + cephfsVolume.path
if err := cephfsVolume.mounter.Mount(src, mountpoint, "ceph", opt); err != nil { mountOptions := volume.JoinMountOptions(cephfsVolume.mountOptions, opt)
if err := cephfsVolume.mounter.Mount(src, mountpoint, "ceph", mountOptions); err != nil {
return fmt.Errorf("CephFS: mount failed: %v", err) return fmt.Errorf("CephFS: mount failed: %v", err)
} }
......
...@@ -221,7 +221,8 @@ func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath st ...@@ -221,7 +221,8 @@ func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath st
} }
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -99,6 +99,10 @@ func (plugin *cinderPlugin) RequiresRemount() bool { ...@@ -99,6 +99,10 @@ func (plugin *cinderPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *cinderPlugin) SupportsMountOption() bool {
return true
}
func (plugin *cinderPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *cinderPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -76,6 +76,10 @@ func (plugin *configMapPlugin) RequiresRemount() bool { ...@@ -76,6 +76,10 @@ func (plugin *configMapPlugin) RequiresRemount() bool {
return true return true
} }
func (plugin *configMapPlugin) SupportsMountOption() bool {
return false
}
func (plugin *configMapPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *configMapPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &configMapVolumeMounter{ return &configMapVolumeMounter{
configMapVolume: &configMapVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}}, configMapVolume: &configMapVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}},
......
...@@ -82,6 +82,10 @@ func (plugin *downwardAPIPlugin) RequiresRemount() bool { ...@@ -82,6 +82,10 @@ func (plugin *downwardAPIPlugin) RequiresRemount() bool {
return true return true
} }
func (plugin *downwardAPIPlugin) SupportsMountOption() bool {
return false
}
func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
v := &downwardAPIVolume{ v := &downwardAPIVolume{
volName: spec.Name(), volName: spec.Name(),
......
...@@ -90,6 +90,10 @@ func (plugin *emptyDirPlugin) RequiresRemount() bool { ...@@ -90,6 +90,10 @@ func (plugin *emptyDirPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *emptyDirPlugin) SupportsMountOption() bool {
return false
}
func (plugin *emptyDirPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *emptyDirPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(), &realMountDetector{plugin.host.GetMounter()}, opts) return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(), &realMountDetector{plugin.host.GetMounter()}, opts)
} }
......
...@@ -78,6 +78,10 @@ func (plugin *fcPlugin) RequiresRemount() bool { ...@@ -78,6 +78,10 @@ func (plugin *fcPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *fcPlugin) SupportsMountOption() bool {
return false
}
func (plugin *fcPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *fcPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -166,6 +166,10 @@ func (plugin *flexVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string ...@@ -166,6 +166,10 @@ func (plugin *flexVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string
return volume.NewSpecFromVolume(flexVolume), nil return volume.NewSpecFromVolume(flexVolume), nil
} }
func (plugin *flexVolumePlugin) SupportsMountOption() bool {
return false
}
// Mark the given commands as unsupported. // Mark the given commands as unsupported.
func (plugin *flexVolumePlugin) unsupported(commands ...string) { func (plugin *flexVolumePlugin) unsupported(commands ...string) {
plugin.Lock() plugin.Lock()
......
...@@ -112,6 +112,10 @@ func (p *flockerPlugin) RequiresRemount() bool { ...@@ -112,6 +112,10 @@ func (p *flockerPlugin) RequiresRemount() bool {
return false return false
} }
func (p *flockerPlugin) SupportsMountOption() bool {
return false
}
func (plugin *flockerPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *flockerPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -209,7 +209,8 @@ func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, device ...@@ -209,7 +209,8 @@ func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, device
} }
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -82,6 +82,10 @@ func (plugin *gcePersistentDiskPlugin) RequiresRemount() bool { ...@@ -82,6 +82,10 @@ func (plugin *gcePersistentDiskPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *gcePersistentDiskPlugin) SupportsMountOption() bool {
return true
}
func (plugin *gcePersistentDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *gcePersistentDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -81,6 +81,10 @@ func (plugin *gitRepoPlugin) RequiresRemount() bool { ...@@ -81,6 +81,10 @@ func (plugin *gitRepoPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *gitRepoPlugin) SupportsMountOption() bool {
return false
}
func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &gitRepoVolumeMounter{ return &gitRepoVolumeMounter{
gitRepoVolume: &gitRepoVolume{ gitRepoVolume: &gitRepoVolume{
......
...@@ -116,6 +116,10 @@ func (plugin *glusterfsPlugin) RequiresRemount() bool { ...@@ -116,6 +116,10 @@ func (plugin *glusterfsPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *glusterfsPlugin) SupportsMountOption() bool {
return true
}
func (plugin *glusterfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *glusterfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -161,10 +165,12 @@ func (plugin *glusterfsPlugin) newMounterInternal(spec *volume.Spec, ep *v1.Endp ...@@ -161,10 +165,12 @@ func (plugin *glusterfsPlugin) newMounterInternal(spec *volume.Spec, ep *v1.Endp
pod: pod, pod: pod,
plugin: plugin, plugin: plugin,
}, },
hosts: ep, hosts: ep,
path: source.Path, path: source.Path,
readOnly: readOnly, readOnly: readOnly,
exe: exe}, nil exe: exe,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil
} }
func (plugin *glusterfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { func (plugin *glusterfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
...@@ -209,10 +215,11 @@ type glusterfs struct { ...@@ -209,10 +215,11 @@ type glusterfs struct {
type glusterfsMounter struct { type glusterfsMounter struct {
*glusterfs *glusterfs
hosts *v1.Endpoints hosts *v1.Endpoints
path string path string
readOnly bool readOnly bool
exe exec.Interface exe exec.Interface
mountOptions []string
} }
var _ volume.Mounter = &glusterfsMounter{} var _ volume.Mounter = &glusterfsMounter{}
...@@ -322,7 +329,8 @@ func (b *glusterfsMounter) setUpAtInternal(dir string) error { ...@@ -322,7 +329,8 @@ func (b *glusterfsMounter) setUpAtInternal(dir string) error {
// Avoid mount storm, pick a host randomly. // Avoid mount storm, pick a host randomly.
// Iterate all hosts until mount succeeds. // Iterate all hosts until mount succeeds.
for _, ip := range addrlist { for _, ip := range addrlist {
errs = b.mounter.Mount(ip+":"+b.path, dir, "glusterfs", options) mountOptions := volume.JoinMountOptions(b.mountOptions, options)
errs = b.mounter.Mount(ip+":"+b.path, dir, "glusterfs", mountOptions)
if errs == nil { if errs == nil {
glog.Infof("glusterfs: successfully mounted %s", dir) glog.Infof("glusterfs: successfully mounted %s", dir)
return nil return nil
......
...@@ -83,6 +83,10 @@ func (plugin *hostPathPlugin) RequiresRemount() bool { ...@@ -83,6 +83,10 @@ func (plugin *hostPathPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *hostPathPlugin) SupportsMountOption() bool {
return false
}
func (plugin *hostPathPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *hostPathPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
......
...@@ -60,7 +60,8 @@ func diskSetUp(manager diskManager, b iscsiDiskMounter, volPath string, mounter ...@@ -60,7 +60,8 @@ func diskSetUp(manager diskManager, b iscsiDiskMounter, volPath string, mounter
if b.readOnly { if b.readOnly {
options = append(options, "ro") options = append(options, "ro")
} }
err = mounter.Mount(globalPDPath, volPath, "", options) mountOptions := volume.JoinMountOptions(b.mountOptions, options)
err = mounter.Mount(globalPDPath, volPath, "", mountOptions)
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
......
...@@ -82,6 +82,10 @@ func (plugin *iscsiPlugin) RequiresRemount() bool { ...@@ -82,6 +82,10 @@ func (plugin *iscsiPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *iscsiPlugin) SupportsMountOption() bool {
return true
}
func (plugin *iscsiPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *iscsiPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -121,10 +125,11 @@ func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UI ...@@ -121,10 +125,11 @@ func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UI
iface: iface, iface: iface,
manager: manager, manager: manager,
plugin: plugin}, plugin: plugin},
fsType: iscsi.FSType, fsType: iscsi.FSType,
readOnly: readOnly, readOnly: readOnly,
mounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}, mounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()},
deviceUtil: ioutil.NewDeviceHandler(ioutil.NewIOHandler()), deviceUtil: ioutil.NewDeviceHandler(ioutil.NewIOHandler()),
mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -184,10 +189,11 @@ func (iscsi *iscsiDisk) GetPath() string { ...@@ -184,10 +189,11 @@ func (iscsi *iscsiDisk) GetPath() string {
type iscsiDiskMounter struct { type iscsiDiskMounter struct {
*iscsiDisk *iscsiDisk
readOnly bool readOnly bool
fsType string fsType string
mounter *mount.SafeFormatAndMount mounter *mount.SafeFormatAndMount
deviceUtil ioutil.DeviceUtil deviceUtil ioutil.DeviceUtil
mountOptions []string
} }
var _ volume.Mounter = &iscsiDiskMounter{} var _ volume.Mounter = &iscsiDiskMounter{}
......
...@@ -88,6 +88,10 @@ func (plugin *nfsPlugin) RequiresRemount() bool { ...@@ -88,6 +88,10 @@ func (plugin *nfsPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *nfsPlugin) SupportsMountOption() bool {
return true
}
func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -113,9 +117,10 @@ func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, moun ...@@ -113,9 +117,10 @@ func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, moun
pod: pod, pod: pod,
plugin: plugin, plugin: plugin,
}, },
server: source.Server, server: source.Server,
exportPath: source.Path, exportPath: source.Path,
readOnly: readOnly, readOnly: readOnly,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -207,9 +212,10 @@ func (nfsMounter *nfsMounter) CanMount() error { ...@@ -207,9 +212,10 @@ func (nfsMounter *nfsMounter) CanMount() error {
type nfsMounter struct { type nfsMounter struct {
*nfs *nfs
server string server string
exportPath string exportPath string
readOnly bool readOnly bool
mountOptions []string
} }
var _ volume.Mounter = &nfsMounter{} var _ volume.Mounter = &nfsMounter{}
...@@ -242,7 +248,8 @@ func (b *nfsMounter) SetUpAt(dir string, fsGroup *int64) error { ...@@ -242,7 +248,8 @@ func (b *nfsMounter) SetUpAt(dir string, fsGroup *int64) error {
if b.readOnly { if b.readOnly {
options = append(options, "ro") options = append(options, "ro")
} }
err = b.mounter.Mount(source, dir, "nfs", options) mountOptions := volume.JoinMountOptions(b.mountOptions, options)
err = b.mounter.Mount(source, dir, "nfs", mountOptions)
if err != nil { if err != nil {
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil { if mntErr != nil {
......
...@@ -203,7 +203,8 @@ func (attacher *photonPersistentDiskAttacher) MountDevice(spec *volume.Spec, dev ...@@ -203,7 +203,8 @@ func (attacher *photonPersistentDiskAttacher) MountDevice(spec *volume.Spec, dev
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -79,6 +79,10 @@ func (plugin *photonPersistentDiskPlugin) RequiresRemount() bool { ...@@ -79,6 +79,10 @@ func (plugin *photonPersistentDiskPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *photonPersistentDiskPlugin) SupportsMountOption() bool {
return true
}
func (plugin *photonPersistentDiskPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { func (plugin *photonPersistentDiskPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod.UID, &PhotonDiskUtil{}, plugin.host.GetMounter()) return plugin.newMounterInternal(spec, pod.UID, &PhotonDiskUtil{}, plugin.host.GetMounter())
} }
......
...@@ -107,6 +107,11 @@ type VolumePlugin interface { ...@@ -107,6 +107,11 @@ type VolumePlugin interface {
// information from input. This function is used by volume manager to reconstruct // information from input. This function is used by volume manager to reconstruct
// volume spec by reading the volume directories from disk // volume spec by reading the volume directories from disk
ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error) ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error)
// SupportsMountOption returns true if volume plugins supports Mount options
// Specifying mount options in a volume plugin that doesn't support
// user specified mount options will result in error creating persistent volumes
SupportsMountOption() bool
} }
// PersistentVolumePlugin is an extended interface of VolumePlugin and is used // PersistentVolumePlugin is an extended interface of VolumePlugin and is used
...@@ -146,6 +151,8 @@ const ( ...@@ -146,6 +151,8 @@ const (
// Name of a volume in external cloud that is being provisioned and thus // Name of a volume in external cloud that is being provisioned and thus
// should be ignored by rest of Kubernetes. // should be ignored by rest of Kubernetes.
ProvisionedVolumeName = "placeholder-for-provisioning" ProvisionedVolumeName = "placeholder-for-provisioning"
// Mount options annotations
MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options"
) )
// ProvisionableVolumePlugin is an extended interface of VolumePlugin and is // ProvisionableVolumePlugin is an extended interface of VolumePlugin and is
......
...@@ -77,6 +77,10 @@ func (plugin *testPlugins) RequiresRemount() bool { ...@@ -77,6 +77,10 @@ func (plugin *testPlugins) RequiresRemount() bool {
return false return false
} }
func (plugin *testPlugins) SupportsMountOption() bool {
return false
}
func (plugin *testPlugins) NewMounter(spec *Spec, podRef *v1.Pod, opts VolumeOptions) (Mounter, error) { func (plugin *testPlugins) NewMounter(spec *Spec, podRef *v1.Pod, opts VolumeOptions) (Mounter, error) {
return nil, nil return nil, nil
} }
......
...@@ -175,6 +175,10 @@ func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath st ...@@ -175,6 +175,10 @@ func (plugin *portworxVolumePlugin) ConstructVolumeSpec(volumeName, mountPath st
return volume.NewSpecFromVolume(portworxVolume), nil return volume.NewSpecFromVolume(portworxVolume), nil
} }
func (plugin *portworxVolumePlugin) SupportsMountOption() bool {
return false
}
func getVolumeSource( func getVolumeSource(
spec *volume.Spec) (*v1.PortworxVolumeSource, bool, error) { spec *volume.Spec) (*v1.PortworxVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.PortworxVolume != nil { if spec.Volume != nil && spec.Volume.PortworxVolume != nil {
......
...@@ -92,6 +92,10 @@ func (plugin *projectedPlugin) RequiresRemount() bool { ...@@ -92,6 +92,10 @@ func (plugin *projectedPlugin) RequiresRemount() bool {
return true return true
} }
func (plugin *projectedPlugin) SupportsMountOption() bool {
return false
}
func (plugin *projectedPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *projectedPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &projectedVolumeMounter{ return &projectedVolumeMounter{
projectedVolume: &projectedVolume{ projectedVolume: &projectedVolume{
......
...@@ -118,6 +118,10 @@ func (plugin *quobytePlugin) RequiresRemount() bool { ...@@ -118,6 +118,10 @@ func (plugin *quobytePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *quobytePlugin) SupportsMountOption() bool {
return true
}
func (plugin *quobytePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *quobytePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -169,8 +173,9 @@ func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, ...@@ -169,8 +173,9 @@ func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod,
volume: source.Volume, volume: source.Volume,
plugin: plugin, plugin: plugin,
}, },
registry: source.Registry, registry: source.Registry,
readOnly: readOnly, readOnly: readOnly,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -205,8 +210,9 @@ type quobyte struct { ...@@ -205,8 +210,9 @@ type quobyte struct {
type quobyteMounter struct { type quobyteMounter struct {
*quobyte *quobyte
registry string registry string
readOnly bool readOnly bool
mountOptions []string
} }
var _ volume.Mounter = &quobyteMounter{} var _ volume.Mounter = &quobyteMounter{}
...@@ -249,7 +255,8 @@ func (mounter *quobyteMounter) SetUpAt(dir string, fsGroup *int64) error { ...@@ -249,7 +255,8 @@ func (mounter *quobyteMounter) SetUpAt(dir string, fsGroup *int64) error {
} }
//if a trailing slash is missing we add it here //if a trailing slash is missing we add it here
if err := mounter.mounter.Mount(mounter.correctTraillingSlash(mounter.registry), dir, "quobyte", options); err != nil { mountOptions := volume.JoinMountOptions(mounter.mountOptions, options)
if err := mounter.mounter.Mount(mounter.correctTraillingSlash(mounter.registry), dir, "quobyte", mountOptions); err != nil {
return fmt.Errorf("quobyte: mount failed: %v", err) return fmt.Errorf("quobyte: mount failed: %v", err)
} }
......
...@@ -71,7 +71,8 @@ func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount. ...@@ -71,7 +71,8 @@ func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount.
if (&b).GetAttributes().ReadOnly { if (&b).GetAttributes().ReadOnly {
options = append(options, "ro") options = append(options, "ro")
} }
err = mounter.Mount(globalPDPath, volPath, "", options) mountOptions := volume.JoinMountOptions(b.mountOptions, options)
err = mounter.Mount(globalPDPath, volPath, "", mountOptions)
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
......
...@@ -86,6 +86,10 @@ func (plugin *rbdPlugin) RequiresRemount() bool { ...@@ -86,6 +86,10 @@ func (plugin *rbdPlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *rbdPlugin) SupportsMountOption() bool {
return true
}
func (plugin *rbdPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *rbdPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{ return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce, v1.ReadWriteOnce,
...@@ -136,11 +140,12 @@ func (plugin *rbdPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, ...@@ -136,11 +140,12 @@ func (plugin *rbdPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID,
mounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}, mounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()},
plugin: plugin, plugin: plugin,
}, },
Mon: source.CephMonitors, Mon: source.CephMonitors,
Id: id, Id: id,
Keyring: keyring, Keyring: keyring,
Secret: secret, Secret: secret,
fsType: source.FSType, fsType: source.FSType,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -360,13 +365,14 @@ func (rbd *rbd) GetPath() string { ...@@ -360,13 +365,14 @@ func (rbd *rbd) GetPath() string {
type rbdMounter struct { type rbdMounter struct {
*rbd *rbd
// capitalized so they can be exported in persistRBD() // capitalized so they can be exported in persistRBD()
Mon []string Mon []string
Id string Id string
Keyring string Keyring string
Secret string Secret string
fsType string fsType string
adminSecret string adminSecret string
adminId string adminId string
mountOptions []string
} }
var _ volume.Mounter = &rbdMounter{} var _ volume.Mounter = &rbdMounter{}
......
...@@ -85,6 +85,10 @@ func (plugin *secretPlugin) RequiresRemount() bool { ...@@ -85,6 +85,10 @@ func (plugin *secretPlugin) RequiresRemount() bool {
return true return true
} }
func (plugin *secretPlugin) SupportsMountOption() bool {
return false
}
func (plugin *secretPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { func (plugin *secretPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &secretVolumeMounter{ return &secretVolumeMounter{
secretVolume: &secretVolume{ secretVolume: &secretVolume{
......
...@@ -203,6 +203,10 @@ func (plugin *FakeVolumePlugin) RequiresRemount() bool { ...@@ -203,6 +203,10 @@ func (plugin *FakeVolumePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *FakeVolumePlugin) SupportsMountOption() bool {
return true
}
func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) { func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
plugin.Lock() plugin.Lock()
defer plugin.Unlock() defer plugin.Unlock()
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
...@@ -372,3 +373,43 @@ func UnmountViaEmptyDir(dir string, host VolumeHost, volName string, volSpec Spe ...@@ -372,3 +373,43 @@ func UnmountViaEmptyDir(dir string, host VolumeHost, volName string, volSpec Spe
} }
return wrapped.TearDownAt(dir) return wrapped.TearDownAt(dir)
} }
// MountOptionFromSpec extracts and joins mount options from volume spec with supplied options
func MountOptionFromSpec(spec *Spec, options ...string) []string {
pv := spec.PersistentVolume
if pv != nil {
if mo, ok := pv.Annotations[MountOptionAnnotation]; ok {
moList := strings.Split(mo, ",")
return JoinMountOptions(moList, options)
}
}
return options
}
// MountOptionFromApiPV extracts mount options from api.PersistentVolume
func MountOptionFromApiPV(pv *api.PersistentVolume) []string {
mountOptions := []string{}
if mo, ok := pv.Annotations[MountOptionAnnotation]; ok {
moList := strings.Split(mo, ",")
return JoinMountOptions(moList, mountOptions)
}
return mountOptions
}
// JoinMountOptions joins mount options eliminating duplicates
func JoinMountOptions(userOptions []string, systemOptions []string) []string {
allMountOptions := sets.NewString()
for _, mountOption := range userOptions {
if len(mountOption) > 0 {
allMountOptions.Insert(mountOption)
}
}
for _, mountOption := range systemOptions {
allMountOptions.Insert(mountOption)
}
return allMountOptions.UnsortedList()
}
...@@ -383,6 +383,12 @@ func (og *operationGenerator) GenerateMountVolumeFunc( ...@@ -383,6 +383,12 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
newMounterErr) newMounterErr)
} }
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
if mountCheckError != nil {
return nil, mountCheckError
}
// Get attacher, if possible // Get attacher, if possible
attachableVolumePlugin, _ := attachableVolumePlugin, _ :=
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec) og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
...@@ -867,3 +873,20 @@ func (og *operationGenerator) verifyVolumeIsSafeToDetach( ...@@ -867,3 +873,20 @@ func (og *operationGenerator) verifyVolumeIsSafeToDetach(
volumeToDetach.NodeName) volumeToDetach.NodeName)
return nil return nil
} }
func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
mountOptions := volume.MountOptionFromSpec(volumeToMount.VolumeSpec)
if len(mountOptions) > 0 && !plugin.SupportsMountOption() {
err := fmt.Errorf(
"MountVolume.checkMountOptionSupport failed for volume %q (spec.Name: %q) pod %q (UID: %q) with: %q",
volumeToMount.VolumeName,
volumeToMount.VolumeSpec.Name(),
volumeToMount.PodName,
volumeToMount.Pod.UID,
"Mount options are not supported for this volume type")
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.UnsupportedMountOption, err.Error())
return err
}
return nil
}
...@@ -19,6 +19,7 @@ package volume ...@@ -19,6 +19,7 @@ package volume
import ( import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"reflect"
"strings" "strings"
"testing" "testing"
...@@ -29,6 +30,7 @@ import ( ...@@ -29,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/util/slice"
) )
type testcase struct { type testcase struct {
...@@ -304,7 +306,74 @@ func TestGenerateVolumeName(t *testing.T) { ...@@ -304,7 +306,74 @@ func TestGenerateVolumeName(t *testing.T) {
if v3 != expect { if v3 != expect {
t.Errorf("Expected %s, got %s", expect, v3) t.Errorf("Expected %s, got %s", expect, v3)
} }
}
func TestMountOptionFromSpec(t *testing.T) {
scenarios := map[string]struct {
volume *Spec
expectedMountList []string
systemOptions []string
}{
"volume-with-mount-options": {
volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
},
}),
expectedMountList: []string{"ro", "nfsvers=3"},
systemOptions: nil,
},
"volume-with-bad-mount-options": {
volume: createVolumeSpecWithMountOption("good-mount-opts", "", v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
},
}),
expectedMountList: []string{},
systemOptions: nil,
},
"vol-with-sys-opts": {
volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
},
}),
expectedMountList: []string{"ro", "nfsvers=3", "fsid=100", "hard"},
systemOptions: []string{"fsid=100", "hard"},
},
"vol-with-sys-opts-with-dup": {
volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
},
}),
expectedMountList: []string{"ro", "nfsvers=3", "fsid=100"},
systemOptions: []string{"fsid=100", "ro"},
},
}
for name, scenario := range scenarios {
mountOptions := MountOptionFromSpec(scenario.volume, scenario.systemOptions...)
if !reflect.DeepEqual(slice.SortStrings(mountOptions), slice.SortStrings(scenario.expectedMountList)) {
t.Errorf("for %s expected mount options : %v got %v", name, scenario.expectedMountList, mountOptions)
}
}
}
func createVolumeSpecWithMountOption(name string, mountOptions string, spec v1.PersistentVolumeSpec) *Spec {
annotations := map[string]string{
MountOptionAnnotation: mountOptions,
}
objMeta := metav1.ObjectMeta{
Name: name,
Annotations: annotations,
}
pv := &v1.PersistentVolume{
ObjectMeta: objMeta,
Spec: spec,
}
return &Spec{PersistentVolume: pv}
} }
func checkFnv32(t *testing.T, s string, expected int) { func checkFnv32(t *testing.T, s string, expected int) {
......
...@@ -195,7 +195,8 @@ func (attacher *vsphereVMDKAttacher) MountDevice(spec *volume.Spec, devicePath s ...@@ -195,7 +195,8 @@ func (attacher *vsphereVMDKAttacher) MountDevice(spec *volume.Spec, devicePath s
if notMnt { if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()} diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options) mountOptions := volume.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil { if err != nil {
os.Remove(deviceMountPath) os.Remove(deviceMountPath)
return err return err
......
...@@ -80,6 +80,10 @@ func (plugin *vsphereVolumePlugin) RequiresRemount() bool { ...@@ -80,6 +80,10 @@ func (plugin *vsphereVolumePlugin) RequiresRemount() bool {
return false return false
} }
func (plugin *vsphereVolumePlugin) SupportsMountOption() bool {
return true
}
func (plugin *vsphereVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { func (plugin *vsphereVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod.UID, &VsphereDiskUtil{}, plugin.host.GetMounter()) return plugin.newMounterInternal(spec, pod.UID, &VsphereDiskUtil{}, plugin.host.GetMounter())
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment