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/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()...)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) GetDeviceMountPath(host VolumeHost, spec *Spec) string {
|
func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) string {
|
||||||
fv.GetDeviceMountPathCallCount++
|
fv.GetDeviceMountPathCallCount++
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue