Add support for PersistentVolumeClaim in Attacher/Detacher interface

- Dereference PVCs in kubelet.
- Add getPersistentVolumebySpec to kubelet.
- Call getPersistentVolumebySpec from mount External volumes
- Add applyPVAnnotations to kubelet.
- Delete persistent_claim plugin.
pull/6/head
Sami Wagiaalla 2016-05-10 15:36:38 -04:00
parent 08440b5dcc
commit 56ccd98db8
9 changed files with 235 additions and 508 deletions

View File

@ -44,7 +44,6 @@ import (
"k8s.io/kubernetes/pkg/volume/host_path" "k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/iscsi" "k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/nfs" "k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/persistent_claim"
"k8s.io/kubernetes/pkg/volume/rbd" "k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/secret" "k8s.io/kubernetes/pkg/volume/secret"
// Cloud providers // Cloud providers
@ -72,7 +71,6 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...) allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...) allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...) allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, persistent_claim.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...) allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...) allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)

View File

@ -565,6 +565,166 @@ func TestCleanupOrphanedVolumes(t *testing.T) {
} }
} }
func TestGetPersistentVolumeBySpec(t *testing.T) {
testKubelet := newTestKubelet(t)
kubelet := testKubelet.kubelet
kubeClient := testKubelet.fakeKubeClient
kubeClient.ReactionChain = fake.NewSimpleClientset(&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{}}).ReactionChain
// Test claim does not exist
_, err := kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err == nil {
t.Errorf("Expected claim to not be found")
}
// Test claim found not bound.
claim := api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "myclaim",
Namespace: "test",
},
}
kubeClient.ReactionChain = fake.NewSimpleClientset(&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
claim,
}}).ReactionChain
_, err = kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err == nil {
t.Errorf("Expected a claim not bound error to occur")
}
// Test claim found, bound but PV does not exist
claim = api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "myclaim",
Namespace: "test",
UID: types.UID("myclaimUID123"),
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "myrealvol",
},
}
kubeClient.ReactionChain = fake.NewSimpleClientset(&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
claim,
}}).ReactionChain
_, err = kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err == nil {
t.Errorf("Expected PV not found error to occur")
}
// Test volume found but not bound.
volume := api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "myrealvol",
},
}
kubeClient.ReactionChain = fake.NewSimpleClientset(
&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
claim,
}},
&api.PersistentVolumeList{Items: []api.PersistentVolume{
volume,
}},
).ReactionChain
_, err = kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err == nil {
t.Errorf("Expected PV not bound error to occur")
}
// Test volume found and incorrectly bound
volume = api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "myrealvol",
},
Spec: api.PersistentVolumeSpec{
ClaimRef: &api.ObjectReference{
Name: "myclaim",
Namespace: "test",
UID: types.UID("veryWrongUID"),
},
},
}
kubeClient.ReactionChain = fake.NewSimpleClientset(
&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
claim,
}},
&api.PersistentVolumeList{Items: []api.PersistentVolume{
volume,
}},
).ReactionChain
_, err = kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err == nil {
t.Errorf("Expected wrong UID error to occur")
}
// Test volume found and correctly bound
volume = api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "myrealvol",
},
Spec: api.PersistentVolumeSpec{
ClaimRef: &api.ObjectReference{
Name: "myclaim",
Namespace: "test",
UID: types.UID("myclaimUID123"),
},
},
}
kubeClient.ReactionChain = fake.NewSimpleClientset(
&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
claim,
}},
&api.PersistentVolumeList{Items: []api.PersistentVolume{
volume,
}},
).ReactionChain
foundVolume, err := kubelet.getPersistentVolumeByClaimName("myclaim", "test")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if foundVolume.Name != volume.Name {
t.Errorf("Found incorrect volume expected %v, but got %v", volume, foundVolume)
}
}
func TestApplyPersistentVolumeAnnotations(t *testing.T) {
testKubelet := newTestKubelet(t)
kubelet := testKubelet.kubelet
pod := &api.Pod{}
pv := &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pv",
Annotations: map[string]string{
volumeGidAnnotationKey: "12345",
},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
ClaimRef: &api.ObjectReference{
Name: "claim",
UID: types.UID("abc123"),
},
},
}
kubelet.applyPersistentVolumeAnnotations(pv, pod)
if pod.Spec.SecurityContext == nil {
t.Errorf("Pod SecurityContext was not set")
}
if pod.Spec.SecurityContext.SupplementalGroups[0] != 12345 {
t.Errorf("Pod's SupplementalGroups list does not contain expect group")
}
}
type stubVolume struct { type stubVolume struct {
path string path string
volume.MetricsNil volume.MetricsNil

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path" "path"
"strconv"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
@ -34,6 +35,10 @@ import (
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
) )
const (
volumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
)
// This just exports required functions from kubelet proper, for use by volume // This just exports required functions from kubelet proper, for use by volume
// plugins. // plugins.
type volumeHost struct { type volumeHost struct {
@ -126,8 +131,20 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
return nil, err return nil, err
} }
var volSpec *volume.Spec
if pod.Spec.Volumes[i].VolumeSource.PersistentVolumeClaim != nil {
claimName := pod.Spec.Volumes[i].PersistentVolumeClaim.ClaimName
pv, err := kl.getPersistentVolumeByClaimName(claimName, pod.Namespace)
if err != nil {
glog.Errorf("Could not find persistentVolume for claim %s err %v", claimName, err)
return nil, err
}
kl.applyPersistentVolumeAnnotations(pv, pod)
volSpec = volume.NewSpecFromPersistentVolume(pv, pod.Spec.Volumes[i].PersistentVolumeClaim.ReadOnly)
} else {
volSpec = volume.NewSpecFromVolume(&pod.Spec.Volumes[i])
}
// Try to use a plugin for this volume. // Try to use a plugin for this volume.
volSpec := volume.NewSpecFromVolume(&pod.Spec.Volumes[i])
mounter, err := kl.newVolumeMounterFromPlugins(volSpec, pod, volume.VolumeOptions{RootContext: rootContext}) mounter, err := kl.newVolumeMounterFromPlugins(volSpec, pod, volume.VolumeOptions{RootContext: rootContext})
if err != nil { if err != nil {
glog.Errorf("Could not create volume mounter for pod %s: %v", pod.UID, err) glog.Errorf("Could not create volume mounter for pod %s: %v", pod.UID, err)
@ -137,7 +154,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
// some volumes require attachment before mounter's setup. // some volumes require attachment before mounter's setup.
// The plugin can be nil, but non-nil errors are legitimate errors. // The plugin can be nil, but non-nil errors are legitimate errors.
// For non-nil plugins, Attachment to a node is required before Mounter's setup. // For non-nil plugins, Attachment to a node is required before Mounter's setup.
attacher, err := kl.newVolumeAttacherFromPlugins(volSpec, pod, volume.VolumeOptions{RootContext: rootContext}) attacher, err := kl.newVolumeAttacherFromPlugins(volSpec, pod)
if err != nil { if err != nil {
glog.Errorf("Could not create volume attacher for pod %s: %v", pod.UID, err) glog.Errorf("Could not create volume attacher for pod %s: %v", pod.UID, err)
return nil, err return nil, err
@ -153,7 +170,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
return nil, err return nil, err
} }
deviceMountPath := attacher.GetDeviceMountPath(&volumeHost{kl}, volSpec) deviceMountPath := attacher.GetDeviceMountPath(volSpec)
if err = attacher.MountDevice(volSpec, devicePath, deviceMountPath, kl.mounter); err != nil { if err = attacher.MountDevice(volSpec, devicePath, deviceMountPath, kl.mounter); err != nil {
return nil, err return nil, err
} }
@ -163,7 +180,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
if err != nil { if err != nil {
return nil, err return nil, err
} }
podVolumes[volSpec.Volume.Name] = kubecontainer.VolumeInfo{Mounter: mounter} podVolumes[pod.Spec.Volumes[i].Name] = kubecontainer.VolumeInfo{Mounter: mounter}
} }
return podVolumes, nil return podVolumes, nil
} }
@ -270,6 +287,57 @@ func (kl *Kubelet) getPodVolumesFromDisk() map[string]cleaner {
return currentVolumes return currentVolumes
} }
func (kl *Kubelet) getPersistentVolumeByClaimName(claimName string, namespace string) (*api.PersistentVolume, error) {
claim, err := kl.kubeClient.Core().PersistentVolumeClaims(namespace).Get(claimName)
if err != nil {
glog.Errorf("Error finding claim: %+v\n", claimName)
return nil, err
}
glog.V(5).Infof("Found claim %v ", claim)
if claim.Spec.VolumeName == "" {
return nil, fmt.Errorf("The claim %+v is not yet bound to a volume", claimName)
}
pv, err := kl.kubeClient.Core().PersistentVolumes().Get(claim.Spec.VolumeName)
if err != nil {
glog.Errorf("Error finding persistent volume for claim: %+v\n", claimName)
return nil, err
}
if pv.Spec.ClaimRef == nil {
return nil, fmt.Errorf("The volume is not yet bound to the claim. Expected to find the bind on volume.Spec.ClaimRef: %+v", pv)
}
if pv.Spec.ClaimRef.UID != claim.UID {
return nil, fmt.Errorf("Expected volume.Spec.ClaimRef.UID %+v but have %+v", pv.Spec.ClaimRef.UID, claim.UID)
}
return pv, nil
}
func (kl *Kubelet) applyPersistentVolumeAnnotations(pv *api.PersistentVolume, pod *api.Pod) error {
// If a GID annotation is provided set the GID attribute.
if volumeGid, ok := pv.Annotations[volumeGidAnnotationKey]; ok {
gid, err := strconv.ParseInt(volumeGid, 10, 64)
if err != nil {
return fmt.Errorf("Invalid value for %s %v", volumeGidAnnotationKey, err)
}
if pod.Spec.SecurityContext == nil {
pod.Spec.SecurityContext = &api.PodSecurityContext{}
}
for _, existingGid := range pod.Spec.SecurityContext.SupplementalGroups {
if gid == existingGid {
return nil
}
}
pod.Spec.SecurityContext.SupplementalGroups = append(pod.Spec.SecurityContext.SupplementalGroups, gid)
}
return nil
}
// newVolumeMounterFromPlugins attempts to find a plugin by volume spec, pod // newVolumeMounterFromPlugins attempts to find a plugin by volume spec, pod
// and volume options and then creates a Mounter. // and volume options and then creates a Mounter.
// Returns a valid Unmounter or an error. // Returns a valid Unmounter or an error.
@ -293,7 +361,7 @@ func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *api.Pod,
// - an error if no plugin was found for the volume // - an error if no plugin was found for the volume
// or the attacher failed to instantiate // or the attacher failed to instantiate
// - nil if there is no appropriate attacher for this volume // - nil if there is no appropriate attacher for this volume
func (kl *Kubelet) newVolumeAttacherFromPlugins(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Attacher, error) { func (kl *Kubelet) newVolumeAttacherFromPlugins(spec *volume.Spec, pod *api.Pod) (volume.Attacher, error) {
plugin, err := kl.volumePluginMgr.FindAttachablePluginBySpec(spec) plugin, err := kl.volumePluginMgr.FindAttachablePluginBySpec(spec)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err) return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err)

View File

@ -1,2 +0,0 @@
maintainers:
- childsb

View File

@ -1,19 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors 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 contains the internal representation of persistent
// volume claims.
package persistent_claim

View File

@ -1,109 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"strconv"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/volume"
)
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&persistentClaimPlugin{host: nil}}
}
type persistentClaimPlugin struct {
host volume.VolumeHost
readOnly bool
}
var _ volume.VolumePlugin = &persistentClaimPlugin{}
const (
persistentClaimPluginName = "kubernetes.io/persistent-claim"
volumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
)
func (plugin *persistentClaimPlugin) Init(host volume.VolumeHost) error {
plugin.host = host
return nil
}
func (plugin *persistentClaimPlugin) Name() string {
return persistentClaimPluginName
}
func (plugin *persistentClaimPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.PersistentVolumeClaim != nil
}
func (plugin *persistentClaimPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
claim, err := plugin.host.GetKubeClient().Core().PersistentVolumeClaims(pod.Namespace).Get(spec.Volume.PersistentVolumeClaim.ClaimName)
if err != nil {
glog.Errorf("Error finding claim: %+v\n", spec.Volume.PersistentVolumeClaim.ClaimName)
return nil, err
}
if claim.Spec.VolumeName == "" {
return nil, fmt.Errorf("The claim %+v is not yet bound to a volume", claim.Name)
}
pv, err := plugin.host.GetKubeClient().Core().PersistentVolumes().Get(claim.Spec.VolumeName)
if err != nil {
glog.Errorf("Error finding persistent volume for claim: %+v\n", claim.Name)
return nil, err
}
if pv.Spec.ClaimRef == nil {
glog.Errorf("The volume is not yet bound to the claim. Expected to find the bind on volume.Spec.ClaimRef: %+v", pv)
return nil, err
}
if pv.Spec.ClaimRef.UID != claim.UID {
glog.Errorf("Expected volume.Spec.ClaimRef.UID %+v but have %+v", pv.Spec.ClaimRef.UID, claim.UID)
return nil, err
}
// If a GID annotation is provided set the GID attribute.
if volumeGid, ok := pv.Annotations[volumeGidAnnotationKey]; ok {
gid, err := strconv.ParseInt(volumeGid, 10, 64)
if err != nil {
return nil, fmt.Errorf("Invalid value for %s %v", volumeGidAnnotationKey, err)
}
if pod.Spec.SecurityContext == nil {
pod.Spec.SecurityContext = &api.PodSecurityContext{}
}
pod.Spec.SecurityContext.SupplementalGroups = append(pod.Spec.SecurityContext.SupplementalGroups, gid)
}
mounter, err := plugin.host.NewWrapperMounter(claim.Spec.VolumeName, *volume.NewSpecFromPersistentVolume(pv, spec.ReadOnly), pod, opts)
if err != nil {
glog.Errorf("Error creating mounter for claim: %+v\n", claim.Name)
return nil, err
}
return mounter, nil
}
func (plugin *persistentClaimPlugin) NewUnmounter(_ string, _ types.UID) (volume.Unmounter, error) {
return nil, fmt.Errorf("This will never be called directly. The PV backing this claim has a unmounter. Kubelet uses that unmounter, not this one, when removing orphaned volumes.")
}

View File

@ -1,369 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors 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"
"os"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/types"
utilstrings "k8s.io/kubernetes/pkg/util/strings"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/gce_pd"
"k8s.io/kubernetes/pkg/volume/host_path"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
// newTestHost returns the temp directory and the VolumeHost created.
// Please be sure to cleanup the temp directory once done!
func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) {
tempDir, err := utiltesting.MkTmpdir("persistent_volume_test.")
if err != nil {
t.Fatalf("can't make a temp rootdir: %v", err)
}
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, testProbeVolumePlugins())
}
func TestCanSupport(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost("/somepath/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{Volume: &api.Volume{VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{}}}}) {
t.Errorf("Expected false")
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{}}}) {
t.Errorf("Expected false")
}
}
func TestNewMounter(t *testing.T) {
tests := []struct {
pv *api.PersistentVolume
claim *api.PersistentVolumeClaim
plugin volume.VolumePlugin
podVolume api.VolumeSource
testFunc func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error
expectedFailure bool
}{
{
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",
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "pvA",
},
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimA",
},
},
plugin: gce_pd.ProbeVolumePlugins()[0],
testFunc: func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error {
if !strings.Contains(mounter.GetPath(), utilstrings.EscapeQualifiedNameForDisk(plugin.Name())) {
return fmt.Errorf("mounter path expected to contain plugin name. Got: %s", mounter.GetPath())
}
return nil
},
expectedFailure: false,
},
{
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvB",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/somepath"},
},
ClaimRef: &api.ObjectReference{
Name: "claimB",
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimB",
Namespace: "nsB",
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "pvA",
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimB",
},
},
plugin: host_path.ProbeVolumePlugins(volume.VolumeConfig{})[0],
testFunc: func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error {
if mounter.GetPath() != "/somepath" {
return fmt.Errorf("Expected HostPath.Path /somepath, got: %s", mounter.GetPath())
}
return nil
},
expectedFailure: false,
},
{
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvA",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimA",
Namespace: "nsA",
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "pvA",
},
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimA",
},
},
plugin: gce_pd.ProbeVolumePlugins()[0],
testFunc: func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error {
if mounter != nil {
return fmt.Errorf("Unexpected non-nil mounter: %+v", mounter)
}
return nil
},
expectedFailure: true, // missing pv.Spec.ClaimRef
},
{
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvA",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
ClaimRef: &api.ObjectReference{
Name: "claimB",
UID: types.UID("abc123"),
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimA",
Namespace: "nsA",
UID: types.UID("def456"),
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "pvA",
},
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimA",
},
},
plugin: gce_pd.ProbeVolumePlugins()[0],
testFunc: func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error {
if mounter != nil {
return fmt.Errorf("Unexpected non-nil mounter: %+v", mounter)
}
return nil
},
expectedFailure: true, // mismatched pv.Spec.ClaimRef and pvc
},
{ // Test GID annotation
pv: &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pv",
Annotations: map[string]string{
volumeGidAnnotationKey: "12345",
},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
ClaimRef: &api.ObjectReference{
Name: "claim",
UID: types.UID("abc123"),
},
},
},
claim: &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claim",
UID: types.UID("abc123"),
},
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "pv",
},
Status: api.PersistentVolumeClaimStatus{
Phase: api.ClaimBound,
},
},
podVolume: api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claim",
},
},
plugin: gce_pd.ProbeVolumePlugins()[0],
testFunc: func(mounter volume.Mounter, plugin volume.VolumePlugin, pod *api.Pod) error {
if pod.Spec.SecurityContext == nil {
return fmt.Errorf("Pod SecurityContext was not set")
}
if pod.Spec.SecurityContext.SupplementalGroups[0] != 12345 {
return fmt.Errorf("Pod's SupplementalGroups list does not contain expect group")
}
return nil
},
expectedFailure: false,
},
}
for _, item := range tests {
client := fake.NewSimpleClientset(item.pv, item.claim)
plugMgr := volume.VolumePluginMgr{}
tempDir, vh := newTestHost(t, client)
defer os.RemoveAll(tempDir)
plugMgr.InitPlugins(testProbeVolumePlugins(), vh)
plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &volume.Spec{Volume: &api.Volume{VolumeSource: item.podVolume}}
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.NewMounter(spec, pod, volume.VolumeOptions{})
if !item.expectedFailure {
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter: %v", mounter)
}
}
if err := item.testFunc(mounter, item.plugin, pod); err != nil {
t.Errorf("Unexpected error %+v", err)
}
}
}
func TestNewMounterClaimNotBound(t *testing.T) {
pv := &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pvC",
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
},
},
}
claim := &api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{
Name: "claimC",
Namespace: "nsA",
},
}
podVolume := api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ReadOnly: false,
ClaimName: "claimC",
},
}
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
tempDir, vh := newTestHost(t, client)
defer os.RemoveAll(tempDir)
plugMgr.InitPlugins(testProbeVolumePlugins(), vh)
plug, err := plugMgr.FindPluginByName("kubernetes.io/persistent-claim")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &volume.Spec{Volume: &api.Volume{VolumeSource: podVolume}}
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.NewMounter(spec, pod, volume.VolumeOptions{})
if mounter != nil {
t.Errorf("Expected a nil mounter if the claim wasn't bound")
}
}
func testProbeVolumePlugins() []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins(volume.VolumeConfig{})...)
allPlugins = append(allPlugins, ProbeVolumePlugins()...)
return allPlugins
}

View File

@ -273,7 +273,7 @@ func (fv *FakeVolume) WaitForAttach(spec *Spec, spectimeout time.Duration) (stri
return "", nil return "", nil
} }
func (fv *FakeVolume) GetDeviceMountPath(host VolumeHost, spec *Spec) string { func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) string {
fv.GetDeviceMountPathCallCount++ fv.GetDeviceMountPathCallCount++
return "" return ""
} }

View File

@ -143,7 +143,7 @@ type Attacher interface {
// GetDeviceMountPath returns a path where the device should // GetDeviceMountPath returns a path where the device should
// be mounted after it is attached. This is a global mount // be mounted after it is attached. This is a global mount
// point which should be bind mounted for individual volumes. // point which should be bind mounted for individual volumes.
GetDeviceMountPath(host VolumeHost, spec *Spec) string GetDeviceMountPath(spec *Spec) string
// MountDevice mounts the disk to a global path which // MountDevice mounts the disk to a global path which
// individual pods can then bind mount // individual pods can then bind mount