diff --git a/pkg/api/types.go b/pkg/api/types.go index edf453f4c51..6b62d4e8608 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -195,6 +195,32 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs"` } +// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way + HostPath *HostPathVolumeSource `json:"hostPath"` + // NFS represents an NFS mount on the host that shares a pod's lifetime + NFS *NFSVolumeSource `json:"nfs"` +} + +// used by VolumeSources to describe their mounting/access modes +type AccessModeType string + +const ( + // can be mounted read/write mode to exactly 1 host + ReadWriteOnce AccessModeType = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany AccessModeType = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany AccessModeType = "ReadWriteMany" +) + // HostPathVolumeSource represents a host directory mapped into a pod. type HostPathVolumeSource struct { Path string `json:"path"` diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 2a676ffd4b8..268c311b274 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -109,6 +109,32 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine "` } +// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk" description:"GCE disk resource attached to the host machine on demand"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way + HostPath *HostPathVolumeSource `json:"hostPath" description:"Persistent hostPath volume useful for development and testing"` + // NFS represents an NFS mount on the host that shares a pod's lifetime + NFS *NFSVolumeSource `json:"nfs" description:"Persistent NFS volume that will be mounted in the host machine"` +} + +// used by VolumeSources to describe their mounting/access modes +type AccessModeType string + +const ( + // can be mounted read/write mode to exactly 1 host + ReadWriteOnce AccessModeType = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany AccessModeType = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany AccessModeType = "ReadWriteMany" +) + // HostPathVolumeSource represents bare host directory volume. type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index fdaf8dcd4bd..68baea9dac8 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -82,6 +82,32 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"` } +// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk" description:"GCE disk resource attached to the host machine on demand"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way + HostPath *HostPathVolumeSource `json:"hostPath" description:"Persistent hostPath volume useful for development and testing"` + // NFS represents an NFS mount on the host that shares a pod's lifetime + NFS *NFSVolumeSource `json:"nfs" description:"Persistent NFS volume that will be mounted in the host machine"` +} + +// used by VolumeSources to describe their mounting/access modes +type AccessModeType string + +const ( + // can be mounted read/write mode to exactly 1 host + ReadWriteOnce AccessModeType = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany AccessModeType = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany AccessModeType = "ReadWriteMany" +) + // HostPathVolumeSource represents bare host directory volume. // // https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md#hostdir diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 7848f2b026a..0c084374e27 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -214,6 +214,32 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"` } +// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk" description:"GCE disk resource attached to the host machine on demand"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way + HostPath *HostPathVolumeSource `json:"hostPath" description:"Persistent hostPath volume useful for development and testing"` + // NFS represents an NFS mount on the host that shares a pod's lifetime + NFS *NFSVolumeSource `json:"nfs" description:"Persistent NFS volume that will be mounted in the host machine"` +} + +// used by VolumeSources to describe their mounting/access modes +type AccessModeType string + +const ( + // can be mounted read/write mode to exactly 1 host + ReadWriteOnce AccessModeType = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany AccessModeType = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany AccessModeType = "ReadWriteMany" +) + // HostPathVolumeSource represents bare host directory volume. type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` diff --git a/pkg/volume/gce_pd/gce_pd.go b/pkg/volume/gce_pd/gce_pd.go index cb1f6ccc996..44d73e5c727 100644 --- a/pkg/volume/gce_pd/gce_pd.go +++ b/pkg/volume/gce_pd/gce_pd.go @@ -71,6 +71,13 @@ func (plugin *gcePersistentDiskPlugin) CanSupport(spec *api.Volume) bool { return false } +func (plugin *gcePersistentDiskPlugin) GetAccessModes() []api.AccessModeType { + return []api.AccessModeType{ + api.ReadWriteOnce, + api.ReadOnlyMany, + } +} + func (plugin *gcePersistentDiskPlugin) NewBuilder(spec *api.Volume, podRef *api.ObjectReference) (volume.Builder, error) { // Inject real implementations here, test through the internal function. return plugin.newBuilderInternal(spec, podRef.UID, &GCEDiskUtil{}, mount.New()) diff --git a/pkg/volume/gce_pd/gce_pd_test.go b/pkg/volume/gce_pd/gce_pd_test.go index 0e7920a4eb5..13330df5d0f 100644 --- a/pkg/volume/gce_pd/gce_pd_test.go +++ b/pkg/volume/gce_pd/gce_pd_test.go @@ -42,6 +42,28 @@ func TestCanSupport(t *testing.T) { } } +func TestGetAccessModes(t *testing.T) { + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) + + plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/gce-pd") + if err != nil { + t.Errorf("Can't find the plugin by name") + } + if !contains(plug.GetAccessModes(), api.ReadWriteOnce) || !contains(plug.GetAccessModes(), api.ReadOnlyMany) { + t.Errorf("Expected two AccessModeTypes: %s and %s", api.ReadWriteOnce, api.ReadOnlyMany) + } +} + +func contains(modes []api.AccessModeType, mode api.AccessModeType) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + type fakePDManager struct{} // TODO(jonesdl) To fully test this, we could create a loopback device diff --git a/pkg/volume/host_path/host_path.go b/pkg/volume/host_path/host_path.go index 54916603c91..12f7b428d59 100644 --- a/pkg/volume/host_path/host_path.go +++ b/pkg/volume/host_path/host_path.go @@ -54,6 +54,12 @@ func (plugin *hostPathPlugin) CanSupport(spec *api.Volume) bool { return false } +func (plugin *hostPathPlugin) GetAccessModes() []api.AccessModeType { + return []api.AccessModeType{ + api.ReadWriteOnce, + } +} + func (plugin *hostPathPlugin) NewBuilder(spec *api.Volume, podRef *api.ObjectReference) (volume.Builder, error) { return &hostPath{spec.HostPath.Path}, nil } diff --git a/pkg/volume/host_path/host_path_test.go b/pkg/volume/host_path/host_path_test.go index 54d50c0436e..aba005337a8 100644 --- a/pkg/volume/host_path/host_path_test.go +++ b/pkg/volume/host_path/host_path_test.go @@ -43,6 +43,19 @@ func TestCanSupport(t *testing.T) { } } +func TestGetAccessModes(t *testing.T) { + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) + + plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/host-path") + if err != nil { + t.Errorf("Can't find the plugin by name") + } + if len(plug.GetAccessModes()) != 1 || plug.GetAccessModes()[0] != api.ReadWriteOnce { + t.Errorf("Expected %s AccessModeType", api.ReadWriteOnce) + } +} + func TestPlugin(t *testing.T) { plugMgr := volume.VolumePluginMgr{} plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("fake", nil, nil)) diff --git a/pkg/volume/nfs/nfs.go b/pkg/volume/nfs/nfs.go index 612b4d34a4e..3beced38686 100644 --- a/pkg/volume/nfs/nfs.go +++ b/pkg/volume/nfs/nfs.go @@ -57,6 +57,14 @@ func (plugin *nfsPlugin) CanSupport(spec *api.Volume) bool { return false } +func (plugin *nfsPlugin) GetAccessModes() []api.AccessModeType { + return []api.AccessModeType{ + api.ReadWriteOnce, + api.ReadOnlyMany, + api.ReadWriteMany, + } +} + func (plugin *nfsPlugin) NewBuilder(spec *api.Volume, podRef *api.ObjectReference) (volume.Builder, error) { return plugin.newBuilderInternal(spec, podRef, plugin.mounter) } diff --git a/pkg/volume/nfs/nfs_test.go b/pkg/volume/nfs/nfs_test.go index ac14123c2e9..fbf6553cc68 100644 --- a/pkg/volume/nfs/nfs_test.go +++ b/pkg/volume/nfs/nfs_test.go @@ -45,6 +45,28 @@ func TestCanSupport(t *testing.T) { } } +func TestGetAccessModes(t *testing.T) { + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil)) + + plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/nfs") + if err != nil { + t.Errorf("Can't find the plugin by name") + } + if !contains(plug.GetAccessModes(), api.ReadWriteOnce) || !contains(plug.GetAccessModes(), api.ReadOnlyMany) || !contains(plug.GetAccessModes(), api.ReadWriteMany) { + t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany) + } +} + +func contains(modes []api.AccessModeType, mode api.AccessModeType) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + type fakeNFSMounter struct { FakeMounter mount.FakeMounter } diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index acbfbd8710c..f11289a20b2 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -59,6 +59,14 @@ type VolumePlugin interface { NewCleaner(name string, podUID types.UID) (Cleaner, error) } +// PersistentVolumePlugin is an extended interface of VolumePlugin and is used +// by volumes that want to provide long term persistence of data +type PersistentVolumePlugin interface { + VolumePlugin + // GetAccessModes describes the ways a given volume can be accessed/mounted. + GetAccessModes() []api.AccessModeType +} + // VolumeHost is an interface that plugins can use to access the kubelet. type VolumeHost interface { // GetPluginDir returns the absolute path to a directory under which @@ -173,3 +181,24 @@ func (pm *VolumePluginMgr) FindPluginByName(name string) (VolumePlugin, error) { } return pm.plugins[matches[0]], nil } + +// FindPersistentPluginBySpec looks for a plugin that can support a given volume +// specification. If no plugins can support or more than one plugin can +// support it, return error. +func (pm *VolumePluginMgr) FindPersistentPluginBySpec(spec *api.Volume) (PersistentVolumePlugin, error) { + volumePlugin, err := pm.FindPluginBySpec(spec) + if err != nil { + return nil, err + } + return volumePlugin.(PersistentVolumePlugin), nil +} + +// FindPluginByName fetches a plugin by name or by legacy name. If no plugin +// is found, returns error. +func (pm *VolumePluginMgr) FindPersistentPluginByName(name string) (PersistentVolumePlugin, error) { + volumePlugin, err := pm.FindPluginByName(name) + if err != nil { + return nil, err + } + return volumePlugin.(PersistentVolumePlugin), nil +} diff --git a/pkg/volume/testing.go b/pkg/volume/testing.go index 6df40451432..0461fbb953f 100644 --- a/pkg/volume/testing.go +++ b/pkg/volume/testing.go @@ -71,7 +71,7 @@ func (f *fakeVolumeHost) NewWrapperCleaner(spec *api.Volume, podUID types.UID) ( return plug.NewCleaner(spec.Name, podUID) } -// FakeVolumePlugin is useful for for testing. It tries to be a fully compliant +// FakeVolumePlugin is useful for testing. It tries to be a fully compliant // plugin, but all it does is make empty directories. // Use as: // volume.RegisterPlugin(&FakePlugin{"fake-name"}) @@ -103,6 +103,10 @@ func (plugin *FakeVolumePlugin) NewCleaner(volName string, podUID types.UID) (Cl return &FakeVolume{podUID, volName, plugin}, nil } +func (plugin *FakeVolumePlugin) GetAccessModes() []api.AccessModeType { + return []api.AccessModeType{} +} + type FakeVolume struct { PodUID types.UID VolName string