mirror of https://github.com/k3s-io/k3s
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
parent
08440b5dcc
commit
56ccd98db8
|
@ -44,7 +44,6 @@ import (
|
|||
"k8s.io/kubernetes/pkg/volume/host_path"
|
||||
"k8s.io/kubernetes/pkg/volume/iscsi"
|
||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||
"k8s.io/kubernetes/pkg/volume/persistent_claim"
|
||||
"k8s.io/kubernetes/pkg/volume/rbd"
|
||||
"k8s.io/kubernetes/pkg/volume/secret"
|
||||
// Cloud providers
|
||||
|
@ -72,7 +71,6 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
|
|||
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, persistent_claim.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)
|
||||
|
|
|
@ -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 {
|
||||
path string
|
||||
volume.MetricsNil
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
@ -34,6 +35,10 @@ import (
|
|||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
const (
|
||||
volumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
|
||||
)
|
||||
|
||||
// This just exports required functions from kubelet proper, for use by volume
|
||||
// plugins.
|
||||
type volumeHost struct {
|
||||
|
@ -126,8 +131,20 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
|
|||
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.
|
||||
volSpec := volume.NewSpecFromVolume(&pod.Spec.Volumes[i])
|
||||
mounter, err := kl.newVolumeMounterFromPlugins(volSpec, pod, volume.VolumeOptions{RootContext: rootContext})
|
||||
if err != nil {
|
||||
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.
|
||||
// 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.
|
||||
attacher, err := kl.newVolumeAttacherFromPlugins(volSpec, pod, volume.VolumeOptions{RootContext: rootContext})
|
||||
attacher, err := kl.newVolumeAttacherFromPlugins(volSpec, pod)
|
||||
if err != nil {
|
||||
glog.Errorf("Could not create volume attacher for pod %s: %v", pod.UID, err)
|
||||
return nil, err
|
||||
|
@ -153,7 +170,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
deviceMountPath := attacher.GetDeviceMountPath(&volumeHost{kl}, volSpec)
|
||||
deviceMountPath := attacher.GetDeviceMountPath(volSpec)
|
||||
if err = attacher.MountDevice(volSpec, devicePath, deviceMountPath, kl.mounter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -163,7 +180,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podVolumes[volSpec.Volume.Name] = kubecontainer.VolumeInfo{Mounter: mounter}
|
||||
podVolumes[pod.Spec.Volumes[i].Name] = kubecontainer.VolumeInfo{Mounter: mounter}
|
||||
}
|
||||
return podVolumes, nil
|
||||
}
|
||||
|
@ -270,6 +287,57 @@ func (kl *Kubelet) getPodVolumesFromDisk() map[string]cleaner {
|
|||
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
|
||||
// and volume options and then creates a Mounter.
|
||||
// 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
|
||||
// or the attacher failed to instantiate
|
||||
// - 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
maintainers:
|
||||
- childsb
|
|
@ -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
|
|
@ -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.")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -273,7 +273,7 @@ func (fv *FakeVolume) WaitForAttach(spec *Spec, spectimeout time.Duration) (stri
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func (fv *FakeVolume) GetDeviceMountPath(host VolumeHost, spec *Spec) string {
|
||||
func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) string {
|
||||
fv.GetDeviceMountPathCallCount++
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ type Attacher interface {
|
|||
// GetDeviceMountPath returns a path where the device should
|
||||
// be mounted after it is attached. This is a global mount
|
||||
// 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
|
||||
// individual pods can then bind mount
|
||||
|
|
Loading…
Reference in New Issue