Skip to content

Commit

Permalink
implement glusterfs volume plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Huamin Chen <hchen@redhat.com>
  • Loading branch information
rootfs committed Apr 7, 2015
1 parent d685172 commit a278cee
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 0 deletions.
3 changes: 3 additions & 0 deletions cmd/kubelet/app/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/empty_dir"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/gce_pd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/git_repo"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/glusterfs"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/host_path"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/iscsi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/nfs"
Expand Down Expand Up @@ -55,6 +56,8 @@ func ProbeVolumePlugins() []volume.VolumePlugin {
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)

return allPlugins
}

Expand Down
3 changes: 3 additions & 0 deletions examples/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ func TestExampleObjectSchemas(t *testing.T) {
"../examples/iscsi/v1beta3": {
"iscsi": &api.Pod{},
},
"../examples/glusterfs/v1beta3": {
"glusterfs": &api.Pod{},
},
}

for path, expected := range cases {
Expand Down
47 changes: 47 additions & 0 deletions examples/glusterfs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Glusterfs

[Glusterfs](http://www.gluster.org) is an open source scale-out filesystem. These examples provide information about how to allow containers use Glusterfs volumes.

The example assumes that the Glusterfs client package is installed on all nodes.

### Prerequisites

Install Glusterfs client package on the Kubernetes hosts.

### Create a POD

The following *volume* spec illustrates a sample configuration.

```js
{
"name": "glusterfsvol",
"glusterfs": {
"endpoints": "glusterfs-cluster",
"path": "kube_vol",
"readOnly": true
}
}
```

The parameters are explained as the followings.

- **endpoints** is endpoints name that represents a Gluster cluster configuration. *kubelet* is optimized to avoid mount storm, it will randomly pick one from the endpoints to mount. If this host is unresponsive, the next Gluster host in the endpoints is automatically selected.
- **path** is the Glusterfs volume name.
- **readOnly** is the boolean that sets the mountpoint readOnly or readWrite.

Detailed POD and Gluster cluster endpoints examples can be found at [v1beta3/](v1beta3/) and [endpoints/](endpoints/)

```shell
# create gluster cluster endpoints
$ kubectl create -f examples/glusterfs/endpoints/glusterfs-endpoints.json
# create a container using gluster volume
$ kubectl create -f examples/glusterfs/v1beta3/glusterfs.json
```
Once that's up you can list the pods and endpoint in the cluster, to verify that the master is running:

```shell
$ kubectl get endpoints
$ kubectl get pods
```

If you ssh to that machine, you can run `docker ps` to see the actual pod and `mount` to see if the Glusterfs volume is mounted.
13 changes: 13 additions & 0 deletions examples/glusterfs/endpoints/glusterfs-endpoints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"apiVersion": "v1beta1",
"id": "glusterfs-cluster",
"kind": "Endpoints",
"metadata": {
"name": "glusterfs-cluster"
},
"Endpoints": [
"10.16.154.81:0",
"10.16.154.82:0",
"10.16.154.83:0"
]
}
32 changes: 32 additions & 0 deletions examples/glusterfs/v1beta3/glusterfs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"apiVersion": "v1beta3",
"id": "glusterfs",
"kind": "Pod",
"metadata": {
"name": "glusterfs"
},
"spec": {
"containers": [
{
"name": "glusterfs",
"image": "kubernetes/pause",
"volumeMounts": [
{
"mountPath": "/mnt/glusterfs",
"name": "glusterfsvol"
}
]
}
],
"volumes": [
{
"name": "glusterfsvol",
"glusterfs": {
"endpoints": "glusterfs-cluster",
"path": "kube_vol",
"readOnly": true
}
}
]
}
}
1 change: 1 addition & 0 deletions pkg/api/testing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
// Exactly one of the fields should be set.
//FIXME: the fuzz can still end up nil. What if fuzz allowed me to say that?
fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo, &vs.Secret, &vs.NFS, &vs.ISCSI)
fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo, &vs.Secret, &vs.NFS, &vs.ISCSI, &vs.Glusterfs)
},
func(d *api.DNSPolicy, c fuzz.Continue) {
policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault}
Expand Down
17 changes: 17 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ type VolumeSource struct {
// ISCSIVolumeSource represents an ISCSI Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
ISCSI *ISCSIVolumeSource `json:"iscsi"`
// Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime
Glusterfs *GlusterfsVolumeSource `json:"glusterfs"`
}

// Similar to VolumeSource but meant for the administrator who creates PVs.
Expand All @@ -210,6 +212,8 @@ type PersistentVolumeSource struct {
// This is useful for development and testing only.
// on-host storage is not supported in any way
HostPath *HostPathVolumeSource `json:"hostPath"`
// Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod
Glusterfs *GlusterfsVolumeSource `json:"glusterfs"`
}

type PersistentVolume struct {
Expand Down Expand Up @@ -421,6 +425,19 @@ type NFSVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty"`
}

// GlusterfsVolumeSource represents a Glusterfs Mount that lasts the lifetime of a pod
type GlusterfsVolumeSource struct {
// Required: EndpointsName is the endpoint name that details Glusterfs topology
EndpointsName string `json:"endpoints"`

// Required: Path is the Glusterfs volume path
Path string `json:"path"`

// Optional: Defaults to false (read/write). ReadOnly here will force
// the Glusterfs to be mounted with read-only permissions
ReadOnly bool `json:"readOnly,omitempty"`
}

// ContainerPort represents a network port in a single container
type ContainerPort struct {
// Optional: If specified, this must be a DNS_LABEL. Each named port
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
return nil
},
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
Expand All @@ -1203,6 +1206,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
return nil
},

Expand Down
17 changes: 17 additions & 0 deletions pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ type VolumeSource struct {
// ISCSI represents an ISCSI Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
ISCSI *ISCSIVolumeSource `json:"iscsi" description:"iSCSI disk attached to host machine on demand"`
// Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume that will be mounted on the host machine "`
}

// Similar to VolumeSource but meant for the administrator who creates PVs.
Expand All @@ -126,6 +128,8 @@ type PersistentVolumeSource struct {
// This is useful for development and testing only.
// on-host storage is not supported in any way.
HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"`
// Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume resource provisioned by an admin"`
}

type PersistentVolume struct {
Expand Down Expand Up @@ -1493,3 +1497,16 @@ type SecretList struct {

Items []Secret `json:"items" description:"items is a list of secret objects"`
}

// GlusterfsVolumeSource represents a Glusterfs Mount that lasts the lifetime of a pod
type GlusterfsVolumeSource struct {
// Required: EndpointsName is the endpoint name that details Glusterfs topology
EndpointsName string `json:"endpoints" description:"gluster hosts endpoints name"`

// Required: Path is the Glusterfs volume path
Path string `json:"path" description:"path to gluster volume"`

// Optional: Defaults to false (read/write). ReadOnly here will force
// the Glusterfs volume to be mounted with read-only permissions
ReadOnly bool `json:"readOnly,omitempty" description:"Glusterfs volume to be mounted with read-only permissions"`
}
6 changes: 6 additions & 0 deletions pkg/api/v1beta2/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
return nil
},
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
Expand All @@ -1130,6 +1133,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
return nil
},

Expand Down
17 changes: 17 additions & 0 deletions pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ type VolumeSource struct {
// ISCSI represents an ISCSI Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
ISCSI *ISCSIVolumeSource `json:"iscsi" description:"iSCSI disk attached to host machine on demand"`
// Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume that will be mounted on the host machine "`
}

// Similar to VolumeSource but meant for the administrator who creates PVs.
Expand All @@ -95,6 +97,8 @@ type PersistentVolumeSource struct {
// This is useful for development and testing only.
// on-host storage is not supported in any way.
HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"`
// Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume resource provisioned by an admin"`
}

type PersistentVolume struct {
Expand Down Expand Up @@ -307,6 +311,19 @@ type ISCSIVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty" description:"read-only if true, read-write otherwise (false or unspecified)"`
}

// GlusterfsVolumeSource represents a Glusterfs Mount that lasts the lifetime of a pod
type GlusterfsVolumeSource struct {
// Required: EndpointsName is the endpoint name that details Glusterfs topology
EndpointsName string `json:"endpoints" description:"gluster hosts endpoints name"`

// Required: Path is the Glusterfs volume path
Path string `json:"path" description:"path to gluster volume"`

// Optional: Defaults to false (read/write). ReadOnly here will force
// the Glusterfs volume to be mounted with read-only permissions
ReadOnly bool `json:"readOnly,omitempty" description:"glusterfs volume to be mounted with read-only permissions"`
}

// VolumeMount describes a mounting of a Volume within a container.
//
// https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md
Expand Down
17 changes: 17 additions & 0 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ type VolumeSource struct {
// ISCSI represents an ISCSI Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
ISCSI *ISCSIVolumeSource `json:"iscsi" description:"iSCSI disk attached to host machine on demand"`
// Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume that will be mounted on the host machine "`
}

// Similar to VolumeSource but meant for the administrator who creates PVs.
Expand All @@ -227,6 +229,8 @@ type PersistentVolumeSource struct {
// This is useful for development and testing only.
// on-host storage is not supported in any way.
HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"`
// Glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume resource provisioned by an admin"`
}

type PersistentVolume struct {
Expand Down Expand Up @@ -343,6 +347,19 @@ type EmptyDirVolumeSource struct {
Medium StorageType `json:"medium" description:"type of storage used to back the volume; must be an empty string (default) or Memory"`
}

// GlusterfsVolumeSource represents a Glusterfs Mount that lasts the lifetime of a pod
type GlusterfsVolumeSource struct {
// Required: EndpointsName is the endpoint name that details Glusterfs topology
EndpointsName string `json:"endpoints" description:"gluster hosts endpoints name"`

// Required: Path is the Glusterfs volume path
Path string `json:"path" description:"path to gluster volume"`

// Optional: Defaults to false (read/write). ReadOnly here will force
// the Glusterfs volume to be mounted with read-only permissions
ReadOnly bool `json:"readOnly,omitempty" description:"glusterfs volume to be mounted with read-only permissions"`
}

// StorageType defines ways that storage can be allocated to a volume.
type StorageType string

Expand Down
15 changes: 15 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ func validateSource(source *api.VolumeSource) errs.ValidationErrorList {
numVolumes++
allErrs = append(allErrs, validateISCSIVolumeSource(source.ISCSI).Prefix("iscsi")...)
}
if source.Glusterfs != nil {
numVolumes++
allErrs = append(allErrs, validateGlusterfs(source.Glusterfs).Prefix("glusterfs")...)
}
if numVolumes != 1 {
allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required"))
}
Expand Down Expand Up @@ -386,6 +390,17 @@ func validateNFS(nfs *api.NFSVolumeSource) errs.ValidationErrorList {
return allErrs
}

func validateGlusterfs(glusterfs *api.GlusterfsVolumeSource) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if glusterfs.EndpointsName == "" {
allErrs = append(allErrs, errs.NewFieldRequired("endpoints"))
}
if glusterfs.Path == "" {
allErrs = append(allErrs, errs.NewFieldRequired("path"))
}
return allErrs
}

func ValidatePersistentVolumeName(name string, prefix bool) (bool, string) {
return nameIsDNSSubdomain(name, prefix)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ func TestValidateVolumes(t *testing.T) {
{Name: "gitrepo", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{"my-repo", "hashstring"}}},
{Name: "iscsidisk", VolumeSource: api.VolumeSource{ISCSI: &api.ISCSIVolumeSource{"127.0.0.1", "iqn.2015-02.example.com:test", 1, "ext4", false}}},
{Name: "secret", VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{"my-secret"}}},
{Name: "glusterfs", VolumeSource: api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{"host1", "path", false}}},
}
names, errs := validateVolumes(successCase)
if len(errs) != 0 {
Expand All @@ -530,6 +531,8 @@ func TestValidateVolumes(t *testing.T) {
emptyVS := api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}
emptyPortal := api.VolumeSource{ISCSI: &api.ISCSIVolumeSource{"", "iqn.2015-02.example.com:test", 1, "ext4", false}}
emptyIQN := api.VolumeSource{ISCSI: &api.ISCSIVolumeSource{"127.0.0.1", "", 1, "ext4", false}}
emptyHosts := api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{"", "path", false}}
emptyPath := api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{"host", "", false}}
errorCases := map[string]struct {
V []api.Volume
T errors.ValidationErrorType
Expand All @@ -541,6 +544,8 @@ func TestValidateVolumes(t *testing.T) {
"name not unique": {[]api.Volume{{Name: "abc", VolumeSource: emptyVS}, {Name: "abc", VolumeSource: emptyVS}}, errors.ValidationErrorTypeDuplicate, "[1].name"},
"empty portal": {[]api.Volume{{Name: "badportal", VolumeSource: emptyPortal}}, errors.ValidationErrorTypeRequired, "[0].source.iscsi.targetPortal"},
"empty iqn": {[]api.Volume{{Name: "badiqn", VolumeSource: emptyIQN}}, errors.ValidationErrorTypeRequired, "[0].source.iscsi.iqn"},
"empty hosts": {[]api.Volume{{Name: "badhost", VolumeSource: emptyHosts}}, errors.ValidationErrorTypeRequired, "[0].source.glusterfs.endpoints"},
"empty path": {[]api.Volume{{Name: "badpath", VolumeSource: emptyPath}}, errors.ValidationErrorTypeRequired, "[0].source.glusterfs.path"},
}
for k, v := range errorCases {
_, errs := validateVolumes(v.V)
Expand Down
Loading

0 comments on commit a278cee

Please sign in to comment.