PVClaim volume plugin

pull/6/head
markturansky 2015-04-14 16:15:42 -04:00
parent afd7d77a24
commit d904e747e3
9 changed files with 325 additions and 0 deletions

View File

@ -33,6 +33,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/host_path"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/iscsi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/nfs"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/persistent_claim"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/secret"
//Cloud providers
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
@ -59,6 +60,7 @@ func ProbeVolumePlugins() []volume.VolumePlugin {
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, persistent_claim.ProbeVolumePlugins()...)
return allPlugins
}

View File

@ -203,6 +203,8 @@ type VolumeSource struct {
ISCSI *ISCSIVolumeSource `json:"iscsi"`
// Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime
Glusterfs *GlusterfsVolumeSource `json:"glusterfs"`
// PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace
PersistentVolumeClaimVolumeSource *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty"`
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
@ -222,6 +224,14 @@ type PersistentVolumeSource struct {
Glusterfs *GlusterfsVolumeSource `json:"glusterfs"`
}
type PersistentVolumeClaimVolumeSource struct {
// ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume
ClaimName string `json:"claimName,omitempty" description:"the name of the claim in the same namespace to be mounted as a volume"`
// Optional: Defaults to false (read/write). ReadOnly here
// will force the ReadOnly setting in VolumeMounts
ReadOnly bool `json:"readOnly,omitempty"`
}
type PersistentVolume struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`

View File

@ -1185,6 +1185,9 @@ func init() {
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
if err := s.Convert(&in.PersistentVolumeClaimVolumeSource, &out.PersistentVolumeClaimVolumeSource, 0); err != nil {
return err
}
return nil
},
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
@ -1212,6 +1215,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.PersistentVolumeClaimVolumeSource, &out.PersistentVolumeClaimVolumeSource, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}

View File

@ -119,6 +119,8 @@ type VolumeSource struct {
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 "`
// PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace
PersistentVolumeClaimVolumeSource *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty" description:"a reference to a PersistentVolumeClaim in the same namespace"`
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
@ -138,6 +140,14 @@ type PersistentVolumeSource struct {
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume resource provisioned by an admin"`
}
type PersistentVolumeClaimVolumeSource struct {
// ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume
ClaimName string `json:"claimName,omitempty" description:"the name of the claim in the same namespace to be mounted as a volume"`
// Optional: Defaults to false (read/write). ReadOnly here
// will force the ReadOnly setting in VolumeMounts
ReadOnly bool `json:"readOnly,omitempty" description:"mount volume as read-only when true; default false"`
}
type PersistentVolume struct {
TypeMeta `json:",inline"`

View File

@ -1112,6 +1112,9 @@ func init() {
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}
if err := s.Convert(&in.PersistentVolumeClaimVolumeSource, &out.PersistentVolumeClaimVolumeSource, 0); err != nil {
return err
}
return nil
},
func(in *VolumeSource, out *newer.VolumeSource, s conversion.Scope) error {
@ -1139,6 +1142,9 @@ func init() {
if err := s.Convert(&in.NFS, &out.NFS, 0); err != nil {
return err
}
if err := s.Convert(&in.PersistentVolumeClaimVolumeSource, &out.PersistentVolumeClaimVolumeSource, 0); err != nil {
return err
}
if err := s.Convert(&in.Glusterfs, &out.Glusterfs, 0); err != nil {
return err
}

View File

@ -88,6 +88,8 @@ type VolumeSource struct {
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 "`
// PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace
PersistentVolumeClaimVolumeSource *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty" description:"a reference to a PersistentVolumeClaim in the same namespace"`
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
@ -107,6 +109,14 @@ type PersistentVolumeSource struct {
Glusterfs *GlusterfsVolumeSource `json:"glusterfs" description:"Glusterfs volume resource provisioned by an admin"`
}
type PersistentVolumeClaimVolumeSource struct {
// ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume
ClaimName string `json:"claimName,omitempty" description:"the name of the claim in the same namespace to be mounted as a volume"`
// Optional: Defaults to false (read/write). ReadOnly here
// will force the ReadOnly setting in VolumeMounts
ReadOnly bool `json:"readOnly,omitempty" description:"mount volume as read-only when true; default false"`
}
type PersistentVolume struct {
TypeMeta `json:",inline"`

View File

@ -220,6 +220,16 @@ type VolumeSource struct {
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 "`
// PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace
PersistentVolumeClaimVolumeSource *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty" description:"a reference to a PersistentVolumeClaim in the same namespace"`
}
type PersistentVolumeClaimVolumeSource struct {
// ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume
ClaimName string `json:"claimName,omitempty" description:"the name of the claim in the same namespace to be mounted as a volume"`
// Optional: Defaults to false (read/write). ReadOnly here
// will force the ReadOnly setting in VolumeMounts
ReadOnly bool `json:"readOnly,omitempty" description:"mount volume as read-only when true; default false"`
}
// Similar to VolumeSource but meant for the administrator who creates PVs.

View File

@ -0,0 +1,77 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package persistent_claim
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
"github.com/golang/glog"
)
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&persistentClaimPlugin{nil}}
}
type persistentClaimPlugin struct {
host volume.VolumeHost
}
var _ volume.VolumePlugin = &persistentClaimPlugin{}
const (
persistentClaimPluginName = "kubernetes.io/persistent-claim"
)
func (plugin *persistentClaimPlugin) Init(host volume.VolumeHost) {
plugin.host = host
}
func (plugin *persistentClaimPlugin) Name() string {
return persistentClaimPluginName
}
func (plugin *persistentClaimPlugin) CanSupport(spec *volume.Spec) bool {
return spec.VolumeSource.PersistentVolumeClaimVolumeSource != nil
}
func (plugin *persistentClaimPlugin) NewBuilder(spec *volume.Spec, podRef *api.ObjectReference, opts volume.VolumeOptions) (volume.Builder, error) {
claim, err := plugin.host.GetKubeClient().PersistentVolumeClaims(podRef.Namespace).Get(spec.VolumeSource.PersistentVolumeClaimVolumeSource.ClaimName)
if err != nil {
glog.Errorf("Error finding claim: %+v\n", spec.VolumeSource.PersistentVolumeClaimVolumeSource.ClaimName)
return nil, err
}
pv, err := plugin.host.GetKubeClient().PersistentVolumes().Get(claim.Status.VolumeRef.Name)
if err != nil {
glog.Errorf("Error finding persistent volume for claim: %+v\n", claim.Name)
return nil, err
}
builder, err := plugin.host.NewWrapperBuilder(volume.NewSpecFromPersistentVolume(pv), podRef, opts)
if err != nil {
glog.Errorf("Error creating builder for claim: %+v\n", claim.Name)
return nil, err
}
return builder, nil
}
func (plugin *persistentClaimPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
return nil, fmt.Errorf("This will never be called directly. The PV backing this claim has a cleaner. Kubelet uses that cleaner, not this one, when removing orphaned volumes.")
}

View File

@ -0,0 +1,194 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package persistent_claim
import (
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/gce_pd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume/host_path"
)
func newTestHost(t *testing.T, fakeKubeClient client.Interface) volume.VolumeHost {
tempDir, err := ioutil.TempDir("/tmp", "persistent_volume_test.")
if err != nil {
t.Fatalf("can't make a temp rootdir: %v", err)
}
return volume.NewFakeVolumeHost(tempDir, fakeKubeClient, testProbeVolumePlugins())
}
func TestCanSupport(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, ProbeVolumePlugins()))
plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/persistent-claim" {
t.Errorf("Wrong name: %s", plug.Name())
}
if !plug.CanSupport(&volume.Spec{Name: "foo", VolumeSource: api.VolumeSource{PersistentVolumeClaimVolumeSource: &api.PersistentVolumeClaimVolumeSource{}}}) {
t.Errorf("Expected true")
}
if plug.CanSupport(&volume.Spec{VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{}}}) {
t.Errorf("Expected false")
}
if plug.CanSupport(&volume.Spec{VolumeSource: api.VolumeSource{}}) {
t.Errorf("Expected false")
}
}
func TestNewBuilder(t *testing.T) {
tests := []struct {
pv *api.PersistentVolume
claim *api.PersistentVolumeClaim
plugin volume.VolumePlugin
podVolume api.VolumeSource
testFunc func(builder volume.Builder, plugin volume.VolumePlugin) error
}{
{
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvA",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
ClaimRef: &api.ObjectReference{
Name: "claimA",
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimA",
Namespace: "nsA",
},
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
VolumeRef: &api.ObjectReference{
Name: "pvA",
},
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaimVolumeSource: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimA",
},
},
plugin: gce_pd.ProbeVolumePlugins()[0],
testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error {
if !strings.Contains(builder.GetPath(), util.EscapeQualifiedNameForDisk(plugin.Name())) {
return fmt.Errorf("builder path expected to contain plugin name. Got: %s", builder.GetPath())
}
return nil
},
},
{
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvB",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/tmp"},
},
ClaimRef: &api.ObjectReference{
Name: "claimB",
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimB",
Namespace: "nsB",
},
Status: api.PersistentVolumeClaimStatus{
VolumeRef: &api.ObjectReference{
Name: "pvB",
},
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaimVolumeSource: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimB",
},
},
plugin: host_path.ProbeVolumePlugins()[0],
testFunc: func(builder volume.Builder, plugin volume.VolumePlugin) error {
if builder.GetPath() != "/tmp" {
return fmt.Errorf("Expected HostPath.Path /tmp, got: %s", builder.GetPath())
}
return nil
},
},
}
for _, item := range tests {
o := testclient.NewObjects(api.Scheme)
o.Add(item.pv)
o.Add(item.claim)
client := &testclient.Fake{ReactFn: testclient.ObjectReaction(o, latest.RESTMapper)}
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(testProbeVolumePlugins(), newTestHost(t, client))
plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &volume.Spec{
Name: "vol1",
VolumeSource: item.podVolume,
}
builder, err := plug.NewBuilder(spec, &api.ObjectReference{UID: types.UID("poduid")}, volume.VolumeOptions{})
if err != nil {
t.Errorf("Failed to make a new Builder: %v", err)
}
if builder == nil {
t.Errorf("Got a nil Builder: %v", builder)
}
if builder != nil {
if err := item.testFunc(builder, item.plugin); err != nil {
t.Errorf("Unexpected error %+v", err)
}
}
}
}
func testProbeVolumePlugins() []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, ProbeVolumePlugins()...)
return allPlugins
}