Skip to content

Commit

Permalink
Implement support for mount options in PVs
Browse files Browse the repository at this point in the history
Add support for mount options via annotations on PVs
  • Loading branch information
gnufied committed Feb 27, 2017
1 parent 7f626cf commit 9306dcc
Show file tree
Hide file tree
Showing 44 changed files with 540 additions and 69 deletions.
26 changes: 26 additions & 0 deletions pkg/api/validation/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"events.go",
"schema.go",
"validation.go",
"volume_plugins.go",
],
tags = ["automanaged"],
deps = [
Expand All @@ -27,6 +28,30 @@ go_library(
"//pkg/capabilities:go_default_library",
"//pkg/features: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/exponent-io/jsonpath",
"//vendor:github.com/golang/glog",
Expand Down Expand Up @@ -77,6 +102,7 @@ go_test(
"//pkg/apis/storage/util:go_default_library",
"//pkg/capabilities:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/volume:go_default_library",
"//vendor:github.com/ghodss/yaml",
"//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/api/testing",
Expand Down
20 changes: 20 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/volume"
)

// TODO: delete this global variable when we enable the validation of common
Expand All @@ -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.
var BannedOwners = genericvalidation.BannedOwners
var volumePlugins []volume.VolumePlugin

func init() {
volumePlugins = probeVolumePlugins()
}

// 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 {
Expand Down Expand Up @@ -1004,6 +1010,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
if pv.Spec.HostPath != nil {
if numVolumes > 0 {
Expand Down
44 changes: 44 additions & 0 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/volume"
)

const (
Expand Down Expand Up @@ -205,6 +206,30 @@ func TestValidatePersistentVolumes(t *testing.T) {
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 {
Expand Down Expand Up @@ -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 {
annotations := map[string]string{
ann: annval,
Expand Down
105 changes: 105 additions & 0 deletions pkg/api/validation/volume_plugins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
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]
}
4 changes: 4 additions & 0 deletions pkg/controller/volume/persistentvolume/framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,10 @@ func (plugin *mockVolumePlugin) RequiresRemount() bool {
return false
}

func (plugin *mockVolumePlugin) SupportsMountOption() bool {
return false
}

func (plugin *mockVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*vol.Spec, error) {
return nil, nil
}
Expand Down
1 change: 1 addition & 0 deletions pkg/kubelet/events/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const (
NodeRebooted = "Rebooted"
ContainerGCFailed = "ContainerGCFailed"
ImageGCFailed = "ImageGCFailed"
UnsupportedMountOption = "UnsupportedMountOption"

// Image manager event reason list
InvalidDiskCapacity = "InvalidDiskCapacity"
Expand Down
2 changes: 2 additions & 0 deletions pkg/volume/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/cloudprovider:go_default_library",
Expand Down Expand Up @@ -58,6 +59,7 @@ go_test(
deps = [
"//pkg/api: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/resource",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
Expand Down
3 changes: 2 additions & 1 deletion pkg/volume/aws_ebs/attacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, dev
}
if notMnt {
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 {
os.Remove(deviceMountPath)
return err
Expand Down
4 changes: 4 additions & 0 deletions pkg/volume/aws_ebs/aws_ebs.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ func (plugin *awsElasticBlockStorePlugin) RequiresRemount() bool {
return false
}

func (plugin *awsElasticBlockStorePlugin) SupportsMountOption() bool {
return true
}

func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
Expand Down
3 changes: 2 additions & 1 deletion pkg/volume/azure_dd/attacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ func (attacher *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath str
}
if notMnt {
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 {
os.Remove(deviceMountPath)
return err
Expand Down
4 changes: 4 additions & 0 deletions pkg/volume/azure_dd/azure_dd.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (plugin *azureDataDiskPlugin) RequiresRemount() bool {
return false
}

func (plugin *azureDataDiskPlugin) SupportsMountOption() bool {
return true
}

func (plugin *azureDataDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
Expand Down
25 changes: 16 additions & 9 deletions pkg/volume/azure_file/azure_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ func (plugin *azureFilePlugin) RequiresRemount() bool {
return false
}

func (plugin *azureFilePlugin) SupportsMountOption() bool {
return true
}

func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
Expand All @@ -105,10 +109,11 @@ func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod
plugin: plugin,
MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
},
util: util,
secretName: source.SecretName,
shareName: source.ShareName,
readOnly: readOnly,
util: util,
secretName: source.SecretName,
shareName: source.ShareName,
readOnly: readOnly,
mountOptions: volume.MountOptionFromSpec(spec),
}, nil
}

Expand Down Expand Up @@ -154,10 +159,11 @@ func (azureFileVolume *azureFile) GetPath() string {

type azureFileMounter struct {
*azureFile
util azureUtil
secretName string
shareName string
readOnly bool
util azureUtil
secretName string
shareName string
readOnly bool
mountOptions []string
}

var _ volume.Mounter = &azureFileMounter{}
Expand Down Expand Up @@ -202,7 +208,8 @@ func (b *azureFileMounter) SetUpAt(dir string, fsGroup *int64) error {
if b.readOnly {
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 {
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil {
Expand Down
Loading

0 comments on commit 9306dcc

Please sign in to comment.