mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Dynamic provisioning V2 controller, provisioners, docs and tests.
This commit is contained in:
parent
214c916045
commit
6e4d95f646
@ -419,19 +419,16 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig
|
|||||||
glog.Infof("Not starting %s apis", groupVersion)
|
glog.Infof("Not starting %s apis", groupVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioner, err := NewVolumeProvisioner(cloud, s.VolumeConfiguration)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("A Provisioner could not be created: %v, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeController := persistentvolumecontroller.NewPersistentVolumeController(
|
volumeController := persistentvolumecontroller.NewPersistentVolumeController(
|
||||||
clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "persistent-volume-binder")),
|
clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "persistent-volume-binder")),
|
||||||
s.PVClaimBinderSyncPeriod.Duration,
|
s.PVClaimBinderSyncPeriod.Duration,
|
||||||
provisioner,
|
ProbeControllerVolumePlugins(cloud, s.VolumeConfiguration),
|
||||||
ProbeRecyclableVolumePlugins(s.VolumeConfiguration),
|
|
||||||
cloud,
|
cloud,
|
||||||
s.ClusterName,
|
s.ClusterName,
|
||||||
nil, nil, nil,
|
nil, // volumeSource
|
||||||
|
nil, // claimSource
|
||||||
|
nil, // classSource
|
||||||
|
nil, // eventRecorder
|
||||||
s.VolumeConfiguration.EnableDynamicProvisioning,
|
s.VolumeConfiguration.EnableDynamicProvisioning,
|
||||||
)
|
)
|
||||||
volumeController.Run()
|
volumeController.Run()
|
||||||
|
@ -62,8 +62,10 @@ func ProbeAttachableVolumePlugins(config componentconfig.VolumeConfiguration) []
|
|||||||
return allPlugins
|
return allPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProbeRecyclableVolumePlugins collects all persistent volume plugins into an easy to use list.
|
// ProbeControllerVolumePlugins collects all persistent volume plugins into an
|
||||||
func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
|
// easy to use list. Only volume plugins that implement any of
|
||||||
|
// provisioner/recycler/deleter interface should be returned.
|
||||||
|
func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
|
||||||
allPlugins := []volume.VolumePlugin{}
|
allPlugins := []volume.VolumePlugin{}
|
||||||
|
|
||||||
// The list of plugins to probe is decided by this binary, not
|
// The list of plugins to probe is decided by this binary, not
|
||||||
@ -79,6 +81,7 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []
|
|||||||
RecyclerMinimumTimeout: int(config.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath),
|
RecyclerMinimumTimeout: int(config.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath),
|
||||||
RecyclerTimeoutIncrement: int(config.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath),
|
RecyclerTimeoutIncrement: int(config.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath),
|
||||||
RecyclerPodTemplate: volume.NewPersistentVolumeRecyclerPodTemplate(),
|
RecyclerPodTemplate: volume.NewPersistentVolumeRecyclerPodTemplate(),
|
||||||
|
ProvisioningEnabled: config.EnableHostPathProvisioning,
|
||||||
}
|
}
|
||||||
if err := AttemptToLoadRecycler(config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, &hostPathConfig); err != nil {
|
if err := AttemptToLoadRecycler(config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, &hostPathConfig); err != nil {
|
||||||
glog.Fatalf("Could not create hostpath recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, err)
|
glog.Fatalf("Could not create hostpath recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, err)
|
||||||
@ -95,34 +98,22 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []
|
|||||||
}
|
}
|
||||||
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...)
|
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...)
|
||||||
|
|
||||||
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
|
if cloud != nil {
|
||||||
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
switch {
|
||||||
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
|
case aws.ProviderName == cloud.ProviderName():
|
||||||
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
|
||||||
|
case gce.ProviderName == cloud.ProviderName():
|
||||||
|
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
||||||
|
case openstack.ProviderName == cloud.ProviderName():
|
||||||
|
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
|
||||||
|
case vsphere.ProviderName == cloud.ProviderName():
|
||||||
|
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return allPlugins
|
return allPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVolumeProvisioner returns a volume provisioner to use when running in a cloud or development environment.
|
|
||||||
// The beta implementation of provisioning allows 1 implied provisioner per cloud, until we allow configuration of many.
|
|
||||||
// We explicitly map clouds to volume plugins here which allows us to configure many later without backwards compatibility issues.
|
|
||||||
// Not all cloudproviders have provisioning capability, which is the reason for the bool in the return to tell the caller to expect one or not.
|
|
||||||
func NewVolumeProvisioner(cloud cloudprovider.Interface, config componentconfig.VolumeConfiguration) (volume.ProvisionableVolumePlugin, error) {
|
|
||||||
switch {
|
|
||||||
case cloud == nil && config.EnableHostPathProvisioning:
|
|
||||||
return getProvisionablePluginFromVolumePlugins(host_path.ProbeVolumePlugins(volume.VolumeConfig{}))
|
|
||||||
case cloud != nil && aws.ProviderName == cloud.ProviderName():
|
|
||||||
return getProvisionablePluginFromVolumePlugins(aws_ebs.ProbeVolumePlugins())
|
|
||||||
case cloud != nil && gce.ProviderName == cloud.ProviderName():
|
|
||||||
return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins())
|
|
||||||
case cloud != nil && openstack.ProviderName == cloud.ProviderName():
|
|
||||||
return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins())
|
|
||||||
case cloud != nil && vsphere.ProviderName == cloud.ProviderName():
|
|
||||||
return getProvisionablePluginFromVolumePlugins(vsphere_volume.ProbeVolumePlugins())
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProvisionablePluginFromVolumePlugins(plugins []volume.VolumePlugin) (volume.ProvisionableVolumePlugin, error) {
|
func getProvisionablePluginFromVolumePlugins(plugins []volume.VolumePlugin) (volume.ProvisionableVolumePlugin, error) {
|
||||||
for _, plugin := range plugins {
|
for _, plugin := range plugins {
|
||||||
if provisonablePlugin, ok := plugin.(volume.ProvisionableVolumePlugin); ok {
|
if provisonablePlugin, ok := plugin.(volume.ProvisionableVolumePlugin); ok {
|
||||||
|
@ -285,21 +285,16 @@ func (s *CMServer) Run(_ []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provisioner, err := kubecontrollermanager.NewVolumeProvisioner(cloud, s.VolumeConfiguration)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("A Provisioner could not be created: %v, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeController := persistentvolumecontroller.NewPersistentVolumeController(
|
volumeController := persistentvolumecontroller.NewPersistentVolumeController(
|
||||||
clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "persistent-volume-binder")),
|
clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "persistent-volume-binder")),
|
||||||
s.PVClaimBinderSyncPeriod.Duration,
|
s.PVClaimBinderSyncPeriod.Duration,
|
||||||
provisioner,
|
kubecontrollermanager.ProbeControllerVolumePlugins(cloud, s.VolumeConfiguration),
|
||||||
kubecontrollermanager.ProbeRecyclableVolumePlugins(s.VolumeConfiguration),
|
|
||||||
cloud,
|
cloud,
|
||||||
s.ClusterName,
|
s.ClusterName,
|
||||||
nil,
|
nil, // volumeSource
|
||||||
nil,
|
nil, // claimSource
|
||||||
nil,
|
nil, // classSource
|
||||||
|
nil, // eventRecorder
|
||||||
s.VolumeConfiguration.EnableDynamicProvisioning,
|
s.VolumeConfiguration.EnableDynamicProvisioning,
|
||||||
)
|
)
|
||||||
volumeController.Run()
|
volumeController.Run()
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test single call to syncClaim and syncVolume methods.
|
// Test single call to syncClaim and syncVolume methods.
|
||||||
@ -422,7 +423,7 @@ func TestSync(t *testing.T) {
|
|||||||
noevents, noerrors, testSyncVolume,
|
noevents, noerrors, testSyncVolume,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runSyncTests(t, tests)
|
runSyncTests(t, tests, []*extensions.StorageClass{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||||
@ -469,5 +470,5 @@ func TestMultiSync(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runMultisyncTests(t, tests)
|
runMultisyncTests(t, tests, []*extensions.StorageClass{}, "")
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/record"
|
"k8s.io/kubernetes/pkg/client/record"
|
||||||
@ -108,10 +109,19 @@ const annBindCompleted = "pv.kubernetes.io/bind-completed"
|
|||||||
// pre-bound). Value of this annotation does not matter.
|
// pre-bound). Value of this annotation does not matter.
|
||||||
const annBoundByController = "pv.kubernetes.io/bound-by-controller"
|
const annBoundByController = "pv.kubernetes.io/bound-by-controller"
|
||||||
|
|
||||||
// annClass annotation represents a new field which instructs dynamic
|
// annClass annotation represents the storage class associated with a resource:
|
||||||
// provisioning to choose a particular storage class (aka profile).
|
// - in PersistentVolumeClaim it represents required class to match.
|
||||||
// Value of this annotation should be empty.
|
// Only PersistentVolumes with the same class (i.e. annotation with the same
|
||||||
const annClass = "volume.alpha.kubernetes.io/storage-class"
|
// value) can be bound to the claim. In case no such volume exists, the
|
||||||
|
// controller will provision a new one using StorageClass instance with
|
||||||
|
// the same name as the annotation value.
|
||||||
|
// - in PersistentVolume it represents storage class to which the persistent
|
||||||
|
// volume belongs.
|
||||||
|
const annClass = "volume.beta.kubernetes.io/storage-class"
|
||||||
|
|
||||||
|
// alphaAnnClass annotation represents the previous alpha storage class
|
||||||
|
// annotation. it's no longer used and held here for posterity.
|
||||||
|
const alphaAnnClass = "volume.alpha.kubernetes.io/storage-class"
|
||||||
|
|
||||||
// This annotation is added to a PV that has been dynamically provisioned by
|
// This annotation is added to a PV that has been dynamically provisioned by
|
||||||
// Kubernetes. Its value is name of volume plugin that created the volume.
|
// Kubernetes. Its value is name of volume plugin that created the volume.
|
||||||
@ -148,13 +158,16 @@ type PersistentVolumeController struct {
|
|||||||
claimController *framework.Controller
|
claimController *framework.Controller
|
||||||
claimControllerStopCh chan struct{}
|
claimControllerStopCh chan struct{}
|
||||||
claimSource cache.ListerWatcher
|
claimSource cache.ListerWatcher
|
||||||
|
classReflector *cache.Reflector
|
||||||
|
classReflectorStopCh chan struct{}
|
||||||
|
classSource cache.ListerWatcher
|
||||||
kubeClient clientset.Interface
|
kubeClient clientset.Interface
|
||||||
eventRecorder record.EventRecorder
|
eventRecorder record.EventRecorder
|
||||||
cloud cloudprovider.Interface
|
cloud cloudprovider.Interface
|
||||||
recyclePluginMgr vol.VolumePluginMgr
|
volumePluginMgr vol.VolumePluginMgr
|
||||||
provisioner vol.ProvisionableVolumePlugin
|
|
||||||
enableDynamicProvisioning bool
|
enableDynamicProvisioning bool
|
||||||
clusterName string
|
clusterName string
|
||||||
|
defaultStorageClass string
|
||||||
|
|
||||||
// Cache of the last known version of volumes and claims. This cache is
|
// Cache of the last known version of volumes and claims. This cache is
|
||||||
// thread safe as long as the volumes/claims there are not modified, they
|
// thread safe as long as the volumes/claims there are not modified, they
|
||||||
@ -163,6 +176,7 @@ type PersistentVolumeController struct {
|
|||||||
// it saves newer version to etcd.
|
// it saves newer version to etcd.
|
||||||
volumes persistentVolumeOrderedIndex
|
volumes persistentVolumeOrderedIndex
|
||||||
claims cache.Store
|
claims cache.Store
|
||||||
|
classes cache.Store
|
||||||
|
|
||||||
// Map of scheduled/running operations.
|
// Map of scheduled/running operations.
|
||||||
runningOperations goroutinemap.GoRoutineMap
|
runningOperations goroutinemap.GoRoutineMap
|
||||||
@ -208,7 +222,7 @@ func (ctrl *PersistentVolumeController) syncUnboundClaim(claim *api.PersistentVo
|
|||||||
glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim))
|
glog.V(4).Infof("synchronizing unbound PersistentVolumeClaim[%s]: no volume found", claimToClaimKey(claim))
|
||||||
// No PV could be found
|
// No PV could be found
|
||||||
// OBSERVATION: pvc is "Pending", will retry
|
// OBSERVATION: pvc is "Pending", will retry
|
||||||
if hasAnnotation(claim.ObjectMeta, annClass) {
|
if getClaimClass(claim) != "" {
|
||||||
if err = ctrl.provisionClaim(claim); err != nil {
|
if err = ctrl.provisionClaim(claim); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -965,7 +979,7 @@ func (ctrl *PersistentVolumeController) recycleVolumeOperation(arg interface{})
|
|||||||
|
|
||||||
// Find a plugin.
|
// Find a plugin.
|
||||||
spec := vol.NewSpecFromPersistentVolume(volume, false)
|
spec := vol.NewSpecFromPersistentVolume(volume, false)
|
||||||
plugin, err := ctrl.recyclePluginMgr.FindRecyclablePluginBySpec(spec)
|
plugin, err := ctrl.volumePluginMgr.FindRecyclablePluginBySpec(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// No recycler found. Emit an event and mark the volume Failed.
|
// No recycler found. Emit an event and mark the volume Failed.
|
||||||
if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", "No recycler plugin found for the volume!"); err != nil {
|
if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", "No recycler plugin found for the volume!"); err != nil {
|
||||||
@ -1119,14 +1133,33 @@ func (ctrl *PersistentVolumeController) isVolumeReleased(volume *api.PersistentV
|
|||||||
// (it will be re-used in future provisioner error cases).
|
// (it will be re-used in future provisioner error cases).
|
||||||
func (ctrl *PersistentVolumeController) doDeleteVolume(volume *api.PersistentVolume) error {
|
func (ctrl *PersistentVolumeController) doDeleteVolume(volume *api.PersistentVolume) error {
|
||||||
glog.V(4).Infof("doDeleteVolume [%s]", volume.Name)
|
glog.V(4).Infof("doDeleteVolume [%s]", volume.Name)
|
||||||
// Find a plugin.
|
var err error
|
||||||
spec := vol.NewSpecFromPersistentVolume(volume, false)
|
|
||||||
plugin, err := ctrl.recyclePluginMgr.FindDeletablePluginBySpec(spec)
|
// Find a plugin. Try to find the same plugin that provisioned the volume
|
||||||
if err != nil {
|
var plugin vol.DeletableVolumePlugin
|
||||||
// No deleter found. Emit an event and mark the volume Failed.
|
if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) {
|
||||||
return fmt.Errorf("Error getting deleter volume plugin for volume %q: %v", volume.Name, err)
|
provisionPluginName := volume.Annotations[annDynamicallyProvisioned]
|
||||||
|
if provisionPluginName != "" {
|
||||||
|
plugin, err = ctrl.volumePluginMgr.FindDeletablePluginByName(provisionPluginName)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(3).Infof("did not find a deleter plugin %q for volume %q: %v, will try to find a generic one",
|
||||||
|
provisionPluginName, volume.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spec := vol.NewSpecFromPersistentVolume(volume, false)
|
||||||
|
if plugin == nil {
|
||||||
|
// The plugin that provisioned the volume was not found or the volume
|
||||||
|
// was not dynamically provisioned. Try to find a plugin by spec.
|
||||||
|
plugin, err = ctrl.volumePluginMgr.FindDeletablePluginBySpec(spec)
|
||||||
|
if err != nil {
|
||||||
|
// No deleter found. Emit an event and mark the volume Failed.
|
||||||
|
return fmt.Errorf("Error getting deleter volume plugin for volume %q: %v", volume.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.V(5).Infof("found a deleter plugin %q for volume %q", plugin.GetPluginName(), volume.Name)
|
||||||
|
|
||||||
// Plugin found
|
// Plugin found
|
||||||
deleter, err := plugin.NewDeleter(spec)
|
deleter, err := plugin.NewDeleter(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1187,12 +1220,10 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: find provisionable plugin based on a class/profile
|
plugin, storageClass, err := ctrl.findProvisionablePlugin(claim)
|
||||||
plugin := ctrl.provisioner
|
if err != nil {
|
||||||
if plugin == nil {
|
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", err.Error())
|
||||||
// No provisioner found. Emit an event.
|
glog.V(2).Infof("error finding provisioning plugin for claim %s: %v", claimToClaimKey(claim), err)
|
||||||
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", "No provisioner plugin found for the claim!")
|
|
||||||
glog.V(2).Infof("no provisioner plugin found for claim %s!", claimToClaimKey(claim))
|
|
||||||
// The controller will retry provisioning the volume in every
|
// The controller will retry provisioning the volume in every
|
||||||
// syncVolume() call.
|
// syncVolume() call.
|
||||||
return
|
return
|
||||||
@ -1212,21 +1243,23 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa
|
|||||||
ClusterName: ctrl.clusterName,
|
ClusterName: ctrl.clusterName,
|
||||||
PVName: pvName,
|
PVName: pvName,
|
||||||
PVCName: claim.Name,
|
PVCName: claim.Name,
|
||||||
|
Parameters: storageClass.Parameters,
|
||||||
|
Selector: claim.Spec.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision the volume
|
// Provision the volume
|
||||||
provisioner, err := plugin.NewProvisioner(options)
|
provisioner, err := plugin.NewProvisioner(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
strerr := fmt.Sprintf("Failed to create provisioner: %v", err)
|
strerr := fmt.Sprintf("Failed to create provisioner: %v", err)
|
||||||
glog.V(2).Infof("failed to create provisioner for claim %q: %v", claimToClaimKey(claim), err)
|
glog.V(2).Infof("failed to create provisioner for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
|
||||||
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
|
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
volume, err = provisioner.Provision()
|
volume, err = provisioner.Provision()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
strerr := fmt.Sprintf("Failed to provision volume: %v", err)
|
strerr := fmt.Sprintf("Failed to provision volume with StorageClass %q: %v", storageClass.Name, err)
|
||||||
glog.V(2).Infof("failed to provision volume for claim %q: %v", claimToClaimKey(claim), err)
|
glog.V(2).Infof("failed to provision volume for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err)
|
||||||
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
|
ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1242,6 +1275,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa
|
|||||||
// Add annBoundByController (used in deleting the volume)
|
// Add annBoundByController (used in deleting the volume)
|
||||||
setAnnotation(&volume.ObjectMeta, annBoundByController, "yes")
|
setAnnotation(&volume.ObjectMeta, annBoundByController, "yes")
|
||||||
setAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName())
|
setAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName())
|
||||||
|
setAnnotation(&volume.ObjectMeta, annClass, getClaimClass(claim))
|
||||||
|
|
||||||
// Try to create the PV object several times
|
// Try to create the PV object several times
|
||||||
for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
|
for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ {
|
||||||
@ -1320,3 +1354,40 @@ func (ctrl *PersistentVolumeController) scheduleOperation(operationName string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctrl *PersistentVolumeController) findProvisionablePlugin(claim *api.PersistentVolumeClaim) (vol.ProvisionableVolumePlugin, *extensions.StorageClass, error) {
|
||||||
|
storageClass, err := ctrl.findStorageClass(claim)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a plugin for the class
|
||||||
|
plugin, err := ctrl.volumePluginMgr.FindProvisionablePluginByName(storageClass.Provisioner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return plugin, storageClass, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *PersistentVolumeController) findStorageClass(claim *api.PersistentVolumeClaim) (*extensions.StorageClass, error) {
|
||||||
|
className := getClaimClass(claim)
|
||||||
|
if className == "" {
|
||||||
|
className = ctrl.defaultStorageClass
|
||||||
|
}
|
||||||
|
if className == "" {
|
||||||
|
return nil, fmt.Errorf("No default StorageClass configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
classObj, found, err := ctrl.classes.GetByKey(className)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("StorageClass %q not found", className)
|
||||||
|
}
|
||||||
|
class, ok := classObj.(*extensions.StorageClass)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Cannot convert object to StorageClass: %+v", classObj)
|
||||||
|
}
|
||||||
|
return class, nil
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
unversioned_core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
|
unversioned_core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
|
||||||
@ -47,11 +48,10 @@ import (
|
|||||||
func NewPersistentVolumeController(
|
func NewPersistentVolumeController(
|
||||||
kubeClient clientset.Interface,
|
kubeClient clientset.Interface,
|
||||||
syncPeriod time.Duration,
|
syncPeriod time.Duration,
|
||||||
provisioner vol.ProvisionableVolumePlugin,
|
volumePlugins []vol.VolumePlugin,
|
||||||
recyclers []vol.VolumePlugin,
|
|
||||||
cloud cloudprovider.Interface,
|
cloud cloudprovider.Interface,
|
||||||
clusterName string,
|
clusterName string,
|
||||||
volumeSource, claimSource cache.ListerWatcher,
|
volumeSource, claimSource, classSource cache.ListerWatcher,
|
||||||
eventRecorder record.EventRecorder,
|
eventRecorder record.EventRecorder,
|
||||||
enableDynamicProvisioning bool,
|
enableDynamicProvisioning bool,
|
||||||
) *PersistentVolumeController {
|
) *PersistentVolumeController {
|
||||||
@ -63,25 +63,19 @@ func NewPersistentVolumeController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
controller := &PersistentVolumeController{
|
controller := &PersistentVolumeController{
|
||||||
volumes: newPersistentVolumeOrderedIndex(),
|
volumes: newPersistentVolumeOrderedIndex(),
|
||||||
claims: cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc),
|
claims: cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc),
|
||||||
kubeClient: kubeClient,
|
kubeClient: kubeClient,
|
||||||
eventRecorder: eventRecorder,
|
eventRecorder: eventRecorder,
|
||||||
runningOperations: goroutinemap.NewGoRoutineMap(false /* exponentialBackOffOnError */),
|
runningOperations: goroutinemap.NewGoRoutineMap(false /* exponentialBackOffOnError */),
|
||||||
cloud: cloud,
|
cloud: cloud,
|
||||||
provisioner: provisioner,
|
|
||||||
enableDynamicProvisioning: enableDynamicProvisioning,
|
enableDynamicProvisioning: enableDynamicProvisioning,
|
||||||
clusterName: clusterName,
|
clusterName: clusterName,
|
||||||
createProvisionedPVRetryCount: createProvisionedPVRetryCount,
|
createProvisionedPVRetryCount: createProvisionedPVRetryCount,
|
||||||
createProvisionedPVInterval: createProvisionedPVInterval,
|
createProvisionedPVInterval: createProvisionedPVInterval,
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.recyclePluginMgr.InitPlugins(recyclers, controller)
|
controller.volumePluginMgr.InitPlugins(volumePlugins, controller)
|
||||||
if controller.provisioner != nil {
|
|
||||||
if err := controller.provisioner.Init(controller); err != nil {
|
|
||||||
glog.Errorf("PersistentVolumeController: error initializing provisioner plugin: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if volumeSource == nil {
|
if volumeSource == nil {
|
||||||
volumeSource = &cache.ListWatch{
|
volumeSource = &cache.ListWatch{
|
||||||
@ -107,6 +101,18 @@ func NewPersistentVolumeController(
|
|||||||
}
|
}
|
||||||
controller.claimSource = claimSource
|
controller.claimSource = claimSource
|
||||||
|
|
||||||
|
if classSource == nil {
|
||||||
|
classSource = &cache.ListWatch{
|
||||||
|
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
|
||||||
|
return kubeClient.Extensions().StorageClasses().List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
|
||||||
|
return kubeClient.Extensions().StorageClasses().Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.classSource = classSource
|
||||||
|
|
||||||
_, controller.volumeController = framework.NewIndexerInformer(
|
_, controller.volumeController = framework.NewIndexerInformer(
|
||||||
volumeSource,
|
volumeSource,
|
||||||
&api.PersistentVolume{},
|
&api.PersistentVolume{},
|
||||||
@ -128,6 +134,16 @@ func NewPersistentVolumeController(
|
|||||||
DeleteFunc: controller.deleteClaim,
|
DeleteFunc: controller.deleteClaim,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This is just a cache of StorageClass instances, no special actions are
|
||||||
|
// needed when a class is created/deleted/updated.
|
||||||
|
controller.classes = cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc)
|
||||||
|
controller.classReflector = cache.NewReflector(
|
||||||
|
classSource,
|
||||||
|
&extensions.StorageClass{},
|
||||||
|
controller.classes,
|
||||||
|
syncPeriod,
|
||||||
|
)
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,6 +449,11 @@ func (ctrl *PersistentVolumeController) Run() {
|
|||||||
ctrl.claimControllerStopCh = make(chan struct{})
|
ctrl.claimControllerStopCh = make(chan struct{})
|
||||||
go ctrl.claimController.Run(ctrl.claimControllerStopCh)
|
go ctrl.claimController.Run(ctrl.claimControllerStopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctrl.classReflectorStopCh == nil {
|
||||||
|
ctrl.classReflectorStopCh = make(chan struct{})
|
||||||
|
go ctrl.classReflector.RunUntil(ctrl.classReflectorStopCh)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop gracefully shuts down this controller
|
// Stop gracefully shuts down this controller
|
||||||
@ -440,6 +461,7 @@ func (ctrl *PersistentVolumeController) Stop() {
|
|||||||
glog.V(4).Infof("stopping PersistentVolumeController")
|
glog.V(4).Infof("stopping PersistentVolumeController")
|
||||||
close(ctrl.volumeControllerStopCh)
|
close(ctrl.volumeControllerStopCh)
|
||||||
close(ctrl.claimControllerStopCh)
|
close(ctrl.claimControllerStopCh)
|
||||||
|
close(ctrl.classReflectorStopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -578,3 +600,25 @@ func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bo
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getVolumeClass returns value of annClass annotation or empty string in case
|
||||||
|
// the annotation does not exist.
|
||||||
|
// TODO: change to PersistentVolume.Spec.Class value when this attribute is
|
||||||
|
// introduced.
|
||||||
|
func getVolumeClass(volume *api.PersistentVolume) string {
|
||||||
|
if class, found := volume.Annotations[annClass]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClaimClass returns value of annClass annotation or empty string in case
|
||||||
|
// the annotation does not exist.
|
||||||
|
// TODO: change to PersistentVolumeClaim.Spec.Class value when this attribute is
|
||||||
|
// introduced.
|
||||||
|
func getClaimClass(claim *api.PersistentVolumeClaim) string {
|
||||||
|
if class, found := claim.Annotations[annClass]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -163,7 +163,7 @@ func TestControllerSync(t *testing.T) {
|
|||||||
client := &fake.Clientset{}
|
client := &fake.Clientset{}
|
||||||
volumeSource := framework.NewFakePVControllerSource()
|
volumeSource := framework.NewFakePVControllerSource()
|
||||||
claimSource := framework.NewFakePVCControllerSource()
|
claimSource := framework.NewFakePVCControllerSource()
|
||||||
ctrl := newTestController(client, volumeSource, claimSource, true)
|
ctrl := newTestController(client, volumeSource, claimSource, nil, true)
|
||||||
reactor := newVolumeReactor(client, ctrl, volumeSource, claimSource, test.errors)
|
reactor := newVolumeReactor(client, ctrl, volumeSource, claimSource, test.errors)
|
||||||
for _, claim := range test.initialClaims {
|
for _, claim := range test.initialClaims {
|
||||||
claimSource.Add(claim)
|
claimSource.Add(claim)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test single call to syncVolume, expecting recycling to happen.
|
// Test single call to syncVolume, expecting recycling to happen.
|
||||||
@ -39,7 +40,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject deleter into the controller and call syncVolume. The
|
// Inject deleter into the controller and call syncVolume. The
|
||||||
// deleter simulates one delete() call that succeeds.
|
// deleter simulates one delete() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// delete volume bound by user
|
// delete volume bound by user
|
||||||
@ -51,7 +52,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject deleter into the controller and call syncVolume. The
|
// Inject deleter into the controller and call syncVolume. The
|
||||||
// deleter simulates one delete() call that succeeds.
|
// deleter simulates one delete() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// delete failure - plugin not found
|
// delete failure - plugin not found
|
||||||
@ -70,7 +71,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// delete failure - delete() returns error
|
// delete failure - delete() returns error
|
||||||
@ -80,7 +81,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{errors.New("Mock delete error")}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error")}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// delete success(?) - volume is deleted before doDelete() starts
|
// delete success(?) - volume is deleted before doDelete() starts
|
||||||
@ -90,7 +91,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
// Delete the volume before delete operation starts
|
// Delete the volume before delete operation starts
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
delete(reactor.volumes, "volume8-6")
|
delete(reactor.volumes, "volume8-6")
|
||||||
@ -107,7 +108,7 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
newClaimArray("claim8-7", "uid8-7", "10Gi", "volume8-7", api.ClaimBound),
|
newClaimArray("claim8-7", "uid8-7", "10Gi", "volume8-7", api.ClaimBound),
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
defer reactor.lock.Unlock()
|
defer reactor.lock.Unlock()
|
||||||
// Bind the volume to resurrected claim (this should never
|
// Bind the volume to resurrected claim (this should never
|
||||||
@ -130,10 +131,10 @@ func TestDeleteSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject deleter into the controller and call syncVolume. The
|
// Inject deleter into the controller and call syncVolume. The
|
||||||
// deleter simulates one delete() call that succeeds.
|
// deleter simulates one delete() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runSyncTests(t, tests)
|
runSyncTests(t, tests, []*extensions.StorageClass{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||||
@ -161,9 +162,9 @@ func TestDeleteMultiSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{errors.New("Mock delete error"), nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error"), nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runMultisyncTests(t, tests)
|
runMultisyncTests(t, tests, []*extensions.StorageClass{}, "")
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||||
@ -583,7 +584,7 @@ func newVolumeReactor(client *fake.Clientset, ctrl *PersistentVolumeController,
|
|||||||
return reactor
|
return reactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestController(kubeClient clientset.Interface, volumeSource, claimSource cache.ListerWatcher, enableDynamicProvisioning bool) *PersistentVolumeController {
|
func newTestController(kubeClient clientset.Interface, volumeSource, claimSource, classSource cache.ListerWatcher, enableDynamicProvisioning bool) *PersistentVolumeController {
|
||||||
if volumeSource == nil {
|
if volumeSource == nil {
|
||||||
volumeSource = framework.NewFakePVControllerSource()
|
volumeSource = framework.NewFakePVControllerSource()
|
||||||
}
|
}
|
||||||
@ -593,12 +594,12 @@ func newTestController(kubeClient clientset.Interface, volumeSource, claimSource
|
|||||||
ctrl := NewPersistentVolumeController(
|
ctrl := NewPersistentVolumeController(
|
||||||
kubeClient,
|
kubeClient,
|
||||||
5*time.Second, // sync period
|
5*time.Second, // sync period
|
||||||
nil, // provisioner
|
|
||||||
[]vol.VolumePlugin{}, // recyclers
|
[]vol.VolumePlugin{}, // recyclers
|
||||||
nil, // cloud
|
nil, // cloud
|
||||||
"",
|
"",
|
||||||
volumeSource,
|
volumeSource,
|
||||||
claimSource,
|
claimSource,
|
||||||
|
classSource,
|
||||||
record.NewFakeRecorder(1000), // event recorder
|
record.NewFakeRecorder(1000), // event recorder
|
||||||
enableDynamicProvisioning,
|
enableDynamicProvisioning,
|
||||||
)
|
)
|
||||||
@ -608,27 +609,6 @@ func newTestController(kubeClient clientset.Interface, volumeSource, claimSource
|
|||||||
return ctrl
|
return ctrl
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRecyclePlugin(ctrl *PersistentVolumeController, expectedRecycleCalls []error) {
|
|
||||||
plugin := &mockVolumePlugin{
|
|
||||||
recycleCalls: expectedRecycleCalls,
|
|
||||||
}
|
|
||||||
ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addDeletePlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []error) {
|
|
||||||
plugin := &mockVolumePlugin{
|
|
||||||
deleteCalls: expectedDeleteCalls,
|
|
||||||
}
|
|
||||||
ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addProvisionPlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []error) {
|
|
||||||
plugin := &mockVolumePlugin{
|
|
||||||
provisionCalls: expectedDeleteCalls,
|
|
||||||
}
|
|
||||||
ctrl.provisioner = plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// newVolume returns a new volume with given attributes
|
// newVolume returns a new volume with given attributes
|
||||||
func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) *api.PersistentVolume {
|
func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) *api.PersistentVolume {
|
||||||
volume := api.PersistentVolume{
|
volume := api.PersistentVolume{
|
||||||
@ -664,10 +644,13 @@ func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase a
|
|||||||
if len(annotations) > 0 {
|
if len(annotations) > 0 {
|
||||||
volume.Annotations = make(map[string]string)
|
volume.Annotations = make(map[string]string)
|
||||||
for _, a := range annotations {
|
for _, a := range annotations {
|
||||||
if a != annDynamicallyProvisioned {
|
switch a {
|
||||||
volume.Annotations[a] = "yes"
|
case annDynamicallyProvisioned:
|
||||||
} else {
|
|
||||||
volume.Annotations[a] = mockPluginName
|
volume.Annotations[a] = mockPluginName
|
||||||
|
case annClass:
|
||||||
|
volume.Annotations[a] = "gold"
|
||||||
|
default:
|
||||||
|
volume.Annotations[a] = "yes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -713,6 +696,17 @@ func withMessage(message string, volumes []*api.PersistentVolume) []*api.Persist
|
|||||||
return volumes
|
return volumes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// volumeWithClass saves given class into annClass annotation.
|
||||||
|
// Meant to be used to compose claims specified inline in a test.
|
||||||
|
func volumeWithClass(className string, volumes []*api.PersistentVolume) []*api.PersistentVolume {
|
||||||
|
if volumes[0].Annotations == nil {
|
||||||
|
volumes[0].Annotations = map[string]string{annClass: className}
|
||||||
|
} else {
|
||||||
|
volumes[0].Annotations[annClass] = className
|
||||||
|
}
|
||||||
|
return volumes
|
||||||
|
}
|
||||||
|
|
||||||
// newVolumeArray returns array with a single volume that would be returned by
|
// newVolumeArray returns array with a single volume that would be returned by
|
||||||
// newVolume() with the same parameters.
|
// newVolume() with the same parameters.
|
||||||
func newVolumeArray(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) []*api.PersistentVolume {
|
func newVolumeArray(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) []*api.PersistentVolume {
|
||||||
@ -749,7 +743,12 @@ func newClaim(name, claimUID, capacity, boundToVolume string, phase api.Persiste
|
|||||||
if len(annotations) > 0 {
|
if len(annotations) > 0 {
|
||||||
claim.Annotations = make(map[string]string)
|
claim.Annotations = make(map[string]string)
|
||||||
for _, a := range annotations {
|
for _, a := range annotations {
|
||||||
claim.Annotations[a] = "yes"
|
switch a {
|
||||||
|
case annClass:
|
||||||
|
claim.Annotations[a] = "gold"
|
||||||
|
default:
|
||||||
|
claim.Annotations[a] = "yes"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +771,17 @@ func newClaimArray(name, claimUID, capacity, boundToVolume string, phase api.Per
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// claimWithClass saves given class into annClass annotation.
|
||||||
|
// Meant to be used to compose claims specified inline in a test.
|
||||||
|
func claimWithClass(className string, claims []*api.PersistentVolumeClaim) []*api.PersistentVolumeClaim {
|
||||||
|
if claims[0].Annotations == nil {
|
||||||
|
claims[0].Annotations = map[string]string{annClass: className}
|
||||||
|
} else {
|
||||||
|
claims[0].Annotations[annClass] = className
|
||||||
|
}
|
||||||
|
return claims
|
||||||
|
}
|
||||||
|
|
||||||
func testSyncClaim(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
func testSyncClaim(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||||
return ctrl.syncClaim(test.initialClaims[0])
|
return ctrl.syncClaim(test.initialClaims[0])
|
||||||
}
|
}
|
||||||
@ -793,29 +803,45 @@ type operationType string
|
|||||||
|
|
||||||
const operationDelete = "Delete"
|
const operationDelete = "Delete"
|
||||||
const operationRecycle = "Recycle"
|
const operationRecycle = "Recycle"
|
||||||
const operationProvision = "Provision"
|
|
||||||
|
|
||||||
// wrapTestWithControllerConfig returns a testCall that:
|
// wrapTestWithPluginCalls returns a testCall that:
|
||||||
// - configures controller with recycler, deleter or provisioner which will
|
// - configures controller with a volume plugin that implements recycler,
|
||||||
// return provided errors when a volume is deleted, recycled or provisioned
|
// deleter and provisioner. The plugin retunrs provided errors when a volume
|
||||||
|
// is deleted, recycled or provisioned.
|
||||||
// - calls given testCall
|
// - calls given testCall
|
||||||
func wrapTestWithControllerConfig(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall {
|
func wrapTestWithPluginCalls(expectedRecycleCalls, expectedDeleteCalls []error, expectedProvisionCalls []provisionCall, toWrap testCall) testCall {
|
||||||
expected := expectedOperationCalls
|
|
||||||
|
|
||||||
return func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
return func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||||
switch operation {
|
plugin := &mockVolumePlugin{
|
||||||
case operationDelete:
|
recycleCalls: expectedRecycleCalls,
|
||||||
addDeletePlugin(ctrl, expected)
|
deleteCalls: expectedDeleteCalls,
|
||||||
case operationRecycle:
|
provisionCalls: expectedProvisionCalls,
|
||||||
addRecyclePlugin(ctrl, expected)
|
|
||||||
case operationProvision:
|
|
||||||
addProvisionPlugin(ctrl, expected)
|
|
||||||
}
|
}
|
||||||
|
ctrl.volumePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl)
|
||||||
|
|
||||||
return toWrap(ctrl, reactor, test)
|
return toWrap(ctrl, reactor, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapTestWithReclaimCalls returns a testCall that:
|
||||||
|
// - configures controller with recycler or deleter which will return provided
|
||||||
|
// errors when a volume is deleted or recycled
|
||||||
|
// - calls given testCall
|
||||||
|
func wrapTestWithReclaimCalls(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall {
|
||||||
|
if operation == operationDelete {
|
||||||
|
return wrapTestWithPluginCalls(nil, expectedOperationCalls, nil, toWrap)
|
||||||
|
} else {
|
||||||
|
return wrapTestWithPluginCalls(expectedOperationCalls, nil, nil, toWrap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapTestWithProvisionCalls returns a testCall that:
|
||||||
|
// - configures controller with a provisioner which will return provided errors
|
||||||
|
// when a claim is provisioned
|
||||||
|
// - calls given testCall
|
||||||
|
func wrapTestWithProvisionCalls(expectedProvisionCalls []provisionCall, toWrap testCall) testCall {
|
||||||
|
return wrapTestWithPluginCalls(nil, nil, expectedProvisionCalls, toWrap)
|
||||||
|
}
|
||||||
|
|
||||||
// wrapTestWithInjectedOperation returns a testCall that:
|
// wrapTestWithInjectedOperation returns a testCall that:
|
||||||
// - starts the controller and lets it run original testCall until
|
// - starts the controller and lets it run original testCall until
|
||||||
// scheduleOperation() call. It blocks the controller there and calls the
|
// scheduleOperation() call. It blocks the controller there and calls the
|
||||||
@ -873,13 +899,13 @@ func evaluateTestResults(ctrl *PersistentVolumeController, reactor *volumeReacto
|
|||||||
// 2. Call the tested function (syncClaim/syncVolume) via
|
// 2. Call the tested function (syncClaim/syncVolume) via
|
||||||
// controllerTest.testCall *once*.
|
// controllerTest.testCall *once*.
|
||||||
// 3. Compare resulting volumes and claims with expected volumes and claims.
|
// 3. Compare resulting volumes and claims with expected volumes and claims.
|
||||||
func runSyncTests(t *testing.T, tests []controllerTest) {
|
func runSyncTests(t *testing.T, tests []controllerTest, storageClasses []*extensions.StorageClass) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
glog.V(4).Infof("starting test %q", test.name)
|
glog.V(4).Infof("starting test %q", test.name)
|
||||||
|
|
||||||
// Initialize the controller
|
// Initialize the controller
|
||||||
client := &fake.Clientset{}
|
client := &fake.Clientset{}
|
||||||
ctrl := newTestController(client, nil, nil, true)
|
ctrl := newTestController(client, nil, nil, nil, true)
|
||||||
reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors)
|
reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors)
|
||||||
for _, claim := range test.initialClaims {
|
for _, claim := range test.initialClaims {
|
||||||
ctrl.claims.Add(claim)
|
ctrl.claims.Add(claim)
|
||||||
@ -890,6 +916,15 @@ func runSyncTests(t *testing.T, tests []controllerTest) {
|
|||||||
reactor.volumes[volume.Name] = volume
|
reactor.volumes[volume.Name] = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert classes to []interface{} and forcefully inject them into
|
||||||
|
// controller.
|
||||||
|
storageClassPtrs := make([]interface{}, len(storageClasses))
|
||||||
|
for i, s := range storageClasses {
|
||||||
|
storageClassPtrs[i] = s
|
||||||
|
}
|
||||||
|
// 1 is the resource version
|
||||||
|
ctrl.classes.Replace(storageClassPtrs, "1")
|
||||||
|
|
||||||
// Run the tested functions
|
// Run the tested functions
|
||||||
err := test.test(ctrl, reactor, test)
|
err := test.test(ctrl, reactor, test)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -920,13 +955,22 @@ func runSyncTests(t *testing.T, tests []controllerTest) {
|
|||||||
// 5. When 3. does not do any changes, finish the tests and compare final set
|
// 5. When 3. does not do any changes, finish the tests and compare final set
|
||||||
// of volumes/claims with expected claims/volumes and report differences.
|
// of volumes/claims with expected claims/volumes and report differences.
|
||||||
// Some limit of calls in enforced to prevent endless loops.
|
// Some limit of calls in enforced to prevent endless loops.
|
||||||
func runMultisyncTests(t *testing.T, tests []controllerTest) {
|
func runMultisyncTests(t *testing.T, tests []controllerTest, storageClasses []*extensions.StorageClass, defaultStorageClass string) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
glog.V(4).Infof("starting multisync test %q", test.name)
|
glog.V(4).Infof("starting multisync test %q", test.name)
|
||||||
|
|
||||||
// Initialize the controller
|
// Initialize the controller
|
||||||
client := &fake.Clientset{}
|
client := &fake.Clientset{}
|
||||||
ctrl := newTestController(client, nil, nil, true)
|
ctrl := newTestController(client, nil, nil, nil, true)
|
||||||
|
|
||||||
|
// Convert classes to []interface{} and forcefully inject them into
|
||||||
|
// controller.
|
||||||
|
storageClassPtrs := make([]interface{}, len(storageClasses))
|
||||||
|
for i, s := range storageClasses {
|
||||||
|
storageClassPtrs[i] = s
|
||||||
|
}
|
||||||
|
ctrl.classes.Replace(storageClassPtrs, "1")
|
||||||
|
|
||||||
reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors)
|
reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors)
|
||||||
for _, claim := range test.initialClaims {
|
for _, claim := range test.initialClaims {
|
||||||
ctrl.claims.Add(claim)
|
ctrl.claims.Add(claim)
|
||||||
@ -1022,7 +1066,7 @@ func runMultisyncTests(t *testing.T, tests []controllerTest) {
|
|||||||
// Dummy volume plugin for provisioning, deletion and recycling. It contains
|
// Dummy volume plugin for provisioning, deletion and recycling. It contains
|
||||||
// lists of expected return values to simulate errors.
|
// lists of expected return values to simulate errors.
|
||||||
type mockVolumePlugin struct {
|
type mockVolumePlugin struct {
|
||||||
provisionCalls []error
|
provisionCalls []provisionCall
|
||||||
provisionCallCounter int
|
provisionCallCounter int
|
||||||
deleteCalls []error
|
deleteCalls []error
|
||||||
deleteCallCounter int
|
deleteCallCounter int
|
||||||
@ -1031,6 +1075,11 @@ type mockVolumePlugin struct {
|
|||||||
provisionOptions vol.VolumeOptions
|
provisionOptions vol.VolumeOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type provisionCall struct {
|
||||||
|
expectedParameters map[string]string
|
||||||
|
ret error
|
||||||
|
}
|
||||||
|
|
||||||
var _ vol.VolumePlugin = &mockVolumePlugin{}
|
var _ vol.VolumePlugin = &mockVolumePlugin{}
|
||||||
var _ vol.RecyclableVolumePlugin = &mockVolumePlugin{}
|
var _ vol.RecyclableVolumePlugin = &mockVolumePlugin{}
|
||||||
var _ vol.DeletableVolumePlugin = &mockVolumePlugin{}
|
var _ vol.DeletableVolumePlugin = &mockVolumePlugin{}
|
||||||
@ -1087,8 +1136,12 @@ func (plugin *mockVolumePlugin) Provision() (*api.PersistentVolume, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pv *api.PersistentVolume
|
var pv *api.PersistentVolume
|
||||||
err := plugin.provisionCalls[plugin.provisionCallCounter]
|
call := plugin.provisionCalls[plugin.provisionCallCounter]
|
||||||
if err == nil {
|
if !reflect.DeepEqual(call.expectedParameters, plugin.provisionOptions.Parameters) {
|
||||||
|
glog.Errorf("invalid provisioner call, expected options: %+v, got: %+v", call.expectedParameters, plugin.provisionOptions.Parameters)
|
||||||
|
return nil, fmt.Errorf("Mock plugin error: invalid provisioner call")
|
||||||
|
}
|
||||||
|
if call.ret == nil {
|
||||||
// Create a fake PV with known GCE volume (to match expected volume)
|
// Create a fake PV with known GCE volume (to match expected volume)
|
||||||
pv = &api.PersistentVolume{
|
pv = &api.PersistentVolume{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
@ -1108,8 +1161,8 @@ func (plugin *mockVolumePlugin) Provision() (*api.PersistentVolume, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plugin.provisionCallCounter++
|
plugin.provisionCallCounter++
|
||||||
glog.V(4).Infof("mock plugin Provision call nr. %d, returning %v: %v", plugin.provisionCallCounter, pv, err)
|
glog.V(4).Infof("mock plugin Provision call nr. %d, returning %v: %v", plugin.provisionCallCounter, pv, call.ret)
|
||||||
return pv, err
|
return pv, call.ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deleter interfaces
|
// Deleter interfaces
|
||||||
|
@ -18,12 +18,11 @@ package persistentvolume
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes
|
// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes
|
||||||
@ -126,11 +125,16 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo
|
|||||||
// filter out:
|
// filter out:
|
||||||
// - volumes bound to another claim
|
// - volumes bound to another claim
|
||||||
// - volumes whose labels don't match the claim's selector, if specified
|
// - volumes whose labels don't match the claim's selector, if specified
|
||||||
|
// - volumes in Class that is not requested
|
||||||
if volume.Spec.ClaimRef != nil {
|
if volume.Spec.ClaimRef != nil {
|
||||||
continue
|
continue
|
||||||
} else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) {
|
} else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
claimClass := getClaimClass(claim)
|
||||||
|
if claimClass != "" && claimClass != getVolumeClass(volume) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
volumeQty := volume.Spec.Capacity[api.ResourceStorage]
|
volumeQty := volume.Spec.Capacity[api.ResourceStorage]
|
||||||
volumeSize := volumeQty.Value()
|
volumeSize := volumeQty.Value()
|
||||||
@ -142,17 +146,6 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to provision volumes if the annotation is set even if there
|
|
||||||
// is matching PV. Therefore, do not look for available PV and let
|
|
||||||
// a new volume to be provisioned.
|
|
||||||
//
|
|
||||||
// When provisioner creates a new PV to this claim, an exact match
|
|
||||||
// pre-bound to the claim will be found by the checks above during
|
|
||||||
// subsequent claim sync.
|
|
||||||
if hasAnnotation(claim.ObjectMeta, annClass) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if smallestVolume != nil {
|
if smallestVolume != nil {
|
||||||
// Found a matching volume
|
// Found a matching volume
|
||||||
return smallestVolume, nil
|
return smallestVolume, nil
|
||||||
|
@ -164,7 +164,52 @@ func TestMatchVolume(t *testing.T) {
|
|||||||
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
|
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
|
||||||
Resources: api.ResourceRequirements{
|
Resources: api.ResourceRequirements{
|
||||||
Requests: api.ResourceList{
|
Requests: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"),
|
api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"successful-match-with-class": {
|
||||||
|
expectedMatch: "gce-pd-silver1",
|
||||||
|
claim: &api.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "claim01",
|
||||||
|
Namespace: "myns",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
annClass: "silver",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
|
||||||
|
Selector: &unversioned.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{
|
||||||
|
"should-exist": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: api.ResourceRequirements{
|
||||||
|
Requests: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"successful-match-with-class-and-labels": {
|
||||||
|
expectedMatch: "gce-pd-silver2",
|
||||||
|
claim: &api.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "claim01",
|
||||||
|
Namespace: "myns",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
annClass: "silver",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
|
||||||
|
Resources: api.ResourceRequirements{
|
||||||
|
Requests: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -561,6 +606,29 @@ func createTestVolumes() []*api.PersistentVolume {
|
|||||||
"should-exist": "true",
|
"should-exist": "true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
Capacity: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"),
|
||||||
|
},
|
||||||
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
|
||||||
|
},
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{
|
||||||
|
api.ReadWriteOnce,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
UID: "gce-pd-silver1",
|
||||||
|
Name: "gce0023",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"should-exist": "true",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
annClass: "silver",
|
||||||
|
},
|
||||||
|
},
|
||||||
Spec: api.PersistentVolumeSpec{
|
Spec: api.PersistentVolumeSpec{
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"),
|
api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"),
|
||||||
@ -573,6 +641,46 @@ func createTestVolumes() []*api.PersistentVolume {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
UID: "gce-pd-silver2",
|
||||||
|
Name: "gce0024",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
annClass: "silver",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
Capacity: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceStorage): resource.MustParse("100G"),
|
||||||
|
},
|
||||||
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
|
||||||
|
},
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{
|
||||||
|
api.ReadWriteOnce,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
UID: "gce-pd-gold",
|
||||||
|
Name: "gce0025",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
annClass: "gold",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
Capacity: api.ResourceList{
|
||||||
|
api.ResourceName(api.ResourceStorage): resource.MustParse("50G"),
|
||||||
|
},
|
||||||
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
|
||||||
|
},
|
||||||
|
AccessModes: []api.PersistentVolumeAccessMode{
|
||||||
|
api.ReadWriteOnce,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,7 +740,7 @@ func TestFindingPreboundVolumes(t *testing.T) {
|
|||||||
// pretend the exact match is available but the largest volume is pre-bound to the claim.
|
// pretend the exact match is available but the largest volume is pre-bound to the claim.
|
||||||
pv1.Spec.ClaimRef = nil
|
pv1.Spec.ClaimRef = nil
|
||||||
pv8.Spec.ClaimRef = claimRef
|
pv8.Spec.ClaimRef = claimRef
|
||||||
volume, _ = index.findBestMatchForClaim(claim)
|
volume, _ = index.findBestMatchForClaim(claim, "")
|
||||||
if volume.Name != pv8.Name {
|
if volume.Name != pv8.Name {
|
||||||
t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
|
t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,59 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var class1Parameters = map[string]string{
|
||||||
|
"param1": "value1",
|
||||||
|
}
|
||||||
|
var class2Parameters = map[string]string{
|
||||||
|
"param2": "value2",
|
||||||
|
}
|
||||||
|
var storageClasses = []*extensions.StorageClass{
|
||||||
|
{
|
||||||
|
TypeMeta: unversioned.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "gold",
|
||||||
|
},
|
||||||
|
|
||||||
|
Provisioner: mockPluginName,
|
||||||
|
Parameters: class1Parameters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TypeMeta: unversioned.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "silver",
|
||||||
|
},
|
||||||
|
Provisioner: mockPluginName,
|
||||||
|
Parameters: class2Parameters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// call to storageClass 1, returning an error
|
||||||
|
var provision1Error = provisionCall{
|
||||||
|
ret: errors.New("Moc provisioner error"),
|
||||||
|
expectedParameters: class1Parameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
// call to storageClass 1, returning a valid PV
|
||||||
|
var provision1Success = provisionCall{
|
||||||
|
ret: nil,
|
||||||
|
expectedParameters: class1Parameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
// call to storageClass 2, returning a valid PV
|
||||||
|
var provision2Success = provisionCall{
|
||||||
|
ret: nil,
|
||||||
|
expectedParameters: class2Parameters,
|
||||||
|
}
|
||||||
|
|
||||||
// Test single call to syncVolume, expecting provisioning to happen.
|
// Test single call to syncVolume, expecting provisioning to happen.
|
||||||
// 1. Fill in the controller with initial data
|
// 1. Fill in the controller with initial data
|
||||||
// 2. Call the syncVolume *once*.
|
// 2. Call the syncVolume *once*.
|
||||||
@ -30,14 +81,14 @@ import (
|
|||||||
func TestProvisionSync(t *testing.T) {
|
func TestProvisionSync(t *testing.T) {
|
||||||
tests := []controllerTest{
|
tests := []controllerTest{
|
||||||
{
|
{
|
||||||
// Provision a volume
|
// Provision a volume (with the default class)
|
||||||
"11-1 - successful provision",
|
"11-1 - successful provision with storage class 1",
|
||||||
novolumes,
|
novolumes,
|
||||||
newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
|
||||||
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
// Binding will be completed in the next syncClaim
|
// Binding will be completed in the next syncClaim
|
||||||
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
noevents, noerrors, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision failure - plugin not found
|
// Provision failure - plugin not found
|
||||||
@ -57,7 +108,7 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
||||||
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
||||||
[]string{"Warning ProvisioningFailed"}, noerrors,
|
[]string{"Warning ProvisioningFailed"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{}, testSyncClaim),
|
wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision failure - Provision returns error
|
// Provision failure - Provision returns error
|
||||||
@ -67,40 +118,35 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
||||||
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
||||||
[]string{"Warning ProvisioningFailed"}, noerrors,
|
[]string{"Warning ProvisioningFailed"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{errors.New("Moc provisioner error")}, testSyncClaim),
|
wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision success - there is already a volume available, still
|
// No provisioning if there is a matching volume available
|
||||||
// we provision a new one when requested.
|
|
||||||
"11-6 - provisioning when there is a volume available",
|
"11-6 - provisioning when there is a volume available",
|
||||||
newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
|
newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass),
|
||||||
[]*api.PersistentVolume{
|
newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass),
|
||||||
newVolume("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
|
|
||||||
newVolume("pvc-uid11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
|
||||||
},
|
|
||||||
newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
|
|
||||||
// Binding will be completed in the next syncClaim
|
|
||||||
newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// No provisioning plugin confingure - makes the test fail when
|
// No provisioning plugin confingure - makes the test fail when
|
||||||
// the controller errorneously tries to provision something
|
// the controller errorneously tries to provision something
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision success? - claim is bound before provisioner creates
|
// Provision success? - claim is bound before provisioner creates
|
||||||
// a volume.
|
// a volume.
|
||||||
"11-7 - claim is bound before provisioning",
|
"11-7 - claim is bound before provisioning",
|
||||||
novolumes,
|
novolumes,
|
||||||
newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
|
||||||
newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
|
||||||
// The claim would be bound in next syncClaim
|
// The claim would be bound in next syncClaim
|
||||||
newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationProvision, []error{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
// Create a volume before provisionClaimOperation starts.
|
// Create a volume before provisionClaimOperation starts.
|
||||||
// This similates a parallel controller provisioning the volume.
|
// This similates a parallel controller provisioning the volume.
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned)
|
volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass)
|
||||||
reactor.volumes[volume.Name] = volume
|
reactor.volumes[volume.Name] = volume
|
||||||
reactor.lock.Unlock()
|
reactor.lock.Unlock()
|
||||||
}),
|
}),
|
||||||
@ -110,7 +156,7 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
// second retry succeeds
|
// second retry succeeds
|
||||||
"11-8 - cannot save provisioned volume",
|
"11-8 - cannot save provisioned volume",
|
||||||
novolumes,
|
novolumes,
|
||||||
newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
|
||||||
newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
|
||||||
// Binding will be completed in the next syncClaim
|
// Binding will be completed in the next syncClaim
|
||||||
newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
|
||||||
@ -121,7 +167,7 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
// will succeed.
|
// will succeed.
|
||||||
{"create", "persistentvolumes", errors.New("Mock creation error")},
|
{"create", "persistentvolumes", errors.New("Mock creation error")},
|
||||||
},
|
},
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision success? - cannot save provisioned PV five times,
|
// Provision success? - cannot save provisioned PV five times,
|
||||||
@ -141,8 +187,12 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||||
},
|
},
|
||||||
wrapTestWithControllerConfig(operationDelete, []error{nil},
|
wrapTestWithPluginCalls(
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim)),
|
nil, // recycle calls
|
||||||
|
[]error{nil}, // delete calls
|
||||||
|
[]provisionCall{provision1Success}, // provision calls
|
||||||
|
testSyncClaim,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision failure - cannot save provisioned PV five times,
|
// Provision failure - cannot save provisioned PV five times,
|
||||||
@ -163,7 +213,7 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||||
},
|
},
|
||||||
// No deleteCalls are configured, which results into no deleter plugin available for the volume
|
// No deleteCalls are configured, which results into no deleter plugin available for the volume
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision failure - cannot save provisioned PV five times,
|
// Provision failure - cannot save provisioned PV five times,
|
||||||
@ -183,16 +233,17 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||||
},
|
},
|
||||||
wrapTestWithControllerConfig(
|
wrapTestWithPluginCalls(
|
||||||
operationDelete, []error{
|
nil, // recycle calls
|
||||||
|
[]error{ // delete calls
|
||||||
errors.New("Mock deletion error1"),
|
errors.New("Mock deletion error1"),
|
||||||
errors.New("Mock deletion error2"),
|
errors.New("Mock deletion error2"),
|
||||||
errors.New("Mock deletion error3"),
|
errors.New("Mock deletion error3"),
|
||||||
errors.New("Mock deletion error4"),
|
errors.New("Mock deletion error4"),
|
||||||
errors.New("Mock deletion error5"),
|
errors.New("Mock deletion error5"),
|
||||||
},
|
},
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
[]provisionCall{provision1Success}, // provision calls
|
||||||
),
|
testSyncClaim),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Provision failure - cannot save provisioned PV five times,
|
// Provision failure - cannot save provisioned PV five times,
|
||||||
@ -212,16 +263,37 @@ func TestProvisionSync(t *testing.T) {
|
|||||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||||
},
|
},
|
||||||
wrapTestWithControllerConfig(
|
wrapTestWithPluginCalls(
|
||||||
operationDelete, []error{
|
nil, // recycle calls
|
||||||
|
[]error{ // delete calls
|
||||||
errors.New("Mock deletion error1"),
|
errors.New("Mock deletion error1"),
|
||||||
nil,
|
nil,
|
||||||
},
|
}, // provison calls
|
||||||
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
[]provisionCall{provision1Success},
|
||||||
|
testSyncClaim,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Provision a volume (with non-default class)
|
||||||
|
"11-13 - successful provision with storage class 2",
|
||||||
|
novolumes,
|
||||||
|
volumeWithClass("silver", newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned)),
|
||||||
|
claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)),
|
||||||
|
// Binding will be completed in the next syncClaim
|
||||||
|
claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)),
|
||||||
|
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Provision error - non existing class
|
||||||
|
"11-14 - fail due to non-existing class",
|
||||||
|
novolumes,
|
||||||
|
novolumes,
|
||||||
|
claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)),
|
||||||
|
claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)),
|
||||||
|
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
runSyncTests(t, tests)
|
runSyncTests(t, tests, storageClasses)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||||
@ -244,20 +316,20 @@ func TestProvisionMultiSync(t *testing.T) {
|
|||||||
// Provision a volume with binding
|
// Provision a volume with binding
|
||||||
"12-1 - successful provision",
|
"12-1 - successful provision",
|
||||||
novolumes,
|
novolumes,
|
||||||
newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
|
||||||
newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass),
|
newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
// Binding will be completed in the next syncClaim
|
// Binding will be completed in the next syncClaim
|
||||||
newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
|
newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
|
||||||
noevents, noerrors, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runMultisyncTests(t, tests)
|
runMultisyncTests(t, tests, storageClasses, storageClasses[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When provisioning is disabled, provisioning a claim should instantly return nil
|
// When provisioning is disabled, provisioning a claim should instantly return nil
|
||||||
func TestDisablingDynamicProvisioner(t *testing.T) {
|
func TestDisablingDynamicProvisioner(t *testing.T) {
|
||||||
ctrl := newTestController(nil, nil, nil, false)
|
ctrl := newTestController(nil, nil, nil, nil, false)
|
||||||
retVal := ctrl.provisionClaim(nil)
|
retVal := ctrl.provisionClaim(nil)
|
||||||
if retVal != nil {
|
if retVal != nil {
|
||||||
t.Errorf("Expected nil return but got %v", retVal)
|
t.Errorf("Expected nil return but got %v", retVal)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test single call to syncVolume, expecting recycling to happen.
|
// Test single call to syncVolume, expecting recycling to happen.
|
||||||
@ -39,7 +40,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject recycler into the controller and call syncVolume. The
|
// Inject recycler into the controller and call syncVolume. The
|
||||||
// recycler simulates one recycle() call that succeeds.
|
// recycler simulates one recycle() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// recycle volume bound by user
|
// recycle volume bound by user
|
||||||
@ -51,7 +52,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject recycler into the controller and call syncVolume. The
|
// Inject recycler into the controller and call syncVolume. The
|
||||||
// recycler simulates one recycle() call that succeeds.
|
// recycler simulates one recycle() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// recycle failure - plugin not found
|
// recycle failure - plugin not found
|
||||||
@ -70,7 +71,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// recycle failure - recycle returns error
|
// recycle failure - recycle returns error
|
||||||
@ -80,7 +81,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// recycle success(?) - volume is deleted before doRecycle() starts
|
// recycle success(?) - volume is deleted before doRecycle() starts
|
||||||
@ -90,7 +91,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
// Delete the volume before recycle operation starts
|
// Delete the volume before recycle operation starts
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
delete(reactor.volumes, "volume6-6")
|
delete(reactor.volumes, "volume6-6")
|
||||||
@ -107,7 +108,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
// Mark the volume as Available before the recycler starts
|
// Mark the volume as Available before the recycler starts
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
volume := reactor.volumes["volume6-7"]
|
volume := reactor.volumes["volume6-7"]
|
||||||
@ -128,7 +129,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||||
// Mark the volume as Available before the recycler starts
|
// Mark the volume as Available before the recycler starts
|
||||||
reactor.lock.Lock()
|
reactor.lock.Lock()
|
||||||
volume := reactor.volumes["volume6-8"]
|
volume := reactor.volumes["volume6-8"]
|
||||||
@ -148,7 +149,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
noevents, noerrors,
|
noevents, noerrors,
|
||||||
// Inject recycler into the controller and call syncVolume. The
|
// Inject recycler into the controller and call syncVolume. The
|
||||||
// recycler simulates one recycle() call that succeeds.
|
// recycler simulates one recycle() call that succeeds.
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// volume has unknown reclaim policy - failure expected
|
// volume has unknown reclaim policy - failure expected
|
||||||
@ -160,7 +161,7 @@ func TestRecycleSync(t *testing.T) {
|
|||||||
[]string{"Warning VolumeUnknownReclaimPolicy"}, noerrors, testSyncVolume,
|
[]string{"Warning VolumeUnknownReclaimPolicy"}, noerrors, testSyncVolume,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runSyncTests(t, tests)
|
runSyncTests(t, tests, []*extensions.StorageClass{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||||
@ -188,9 +189,9 @@ func TestRecycleMultiSync(t *testing.T) {
|
|||||||
noclaims,
|
noclaims,
|
||||||
noclaims,
|
noclaims,
|
||||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||||
wrapTestWithControllerConfig(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume),
|
wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
runMultisyncTests(t, tests)
|
runMultisyncTests(t, tests, []*extensions.StorageClass{}, "")
|
||||||
}
|
}
|
||||||
|
@ -491,6 +491,7 @@ var roleColumns = []string{"NAME", "AGE"}
|
|||||||
var roleBindingColumns = []string{"NAME", "AGE"}
|
var roleBindingColumns = []string{"NAME", "AGE"}
|
||||||
var clusterRoleColumns = []string{"NAME", "AGE"}
|
var clusterRoleColumns = []string{"NAME", "AGE"}
|
||||||
var clusterRoleBindingColumns = []string{"NAME", "AGE"}
|
var clusterRoleBindingColumns = []string{"NAME", "AGE"}
|
||||||
|
var storageClassColumns = []string{"NAME", "TYPE"}
|
||||||
|
|
||||||
// TODO: consider having 'KIND' for third party resource data
|
// TODO: consider having 'KIND' for third party resource data
|
||||||
var thirdPartyResourceDataColumns = []string{"NAME", "LABELS", "DATA"}
|
var thirdPartyResourceDataColumns = []string{"NAME", "LABELS", "DATA"}
|
||||||
@ -603,6 +604,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
|
|||||||
h.Handler(clusterRoleBindingColumns, printClusterRoleBindingList)
|
h.Handler(clusterRoleBindingColumns, printClusterRoleBindingList)
|
||||||
h.Handler(certificateSigningRequestColumns, printCertificateSigningRequest)
|
h.Handler(certificateSigningRequestColumns, printCertificateSigningRequest)
|
||||||
h.Handler(certificateSigningRequestColumns, printCertificateSigningRequestList)
|
h.Handler(certificateSigningRequestColumns, printCertificateSigningRequestList)
|
||||||
|
h.Handler(storageClassColumns, printStorageClass)
|
||||||
|
h.Handler(storageClassColumns, printStorageClassList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
||||||
@ -2067,6 +2070,32 @@ func printNetworkPolicyList(list *extensions.NetworkPolicyList, w io.Writer, opt
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printStorageClass(sc *extensions.StorageClass, w io.Writer, options PrintOptions) error {
|
||||||
|
name := sc.Name
|
||||||
|
provtype := sc.Provisioner
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(w, "%s\t%s\t", name, provtype); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprint(w, AppendLabels(sc.Labels, options.ColumnLabels)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, sc.Labels)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStorageClassList(scList *extensions.StorageClassList, w io.Writer, options PrintOptions) error {
|
||||||
|
for _, sc := range scList.Items {
|
||||||
|
if err := printStorageClass(&sc, w, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func AppendLabels(itemLabels map[string]string, columnLabels []string) string {
|
func AppendLabels(itemLabels map[string]string, columnLabels []string) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
@ -43,17 +43,6 @@ func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProbeRecyclableVolumePlugins(recyclerFunc func(pvName string, spec *volume.Spec, host volume.VolumeHost, volumeConfig volume.VolumeConfig) (volume.Recycler, error), volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
|
|
||||||
return []volume.VolumePlugin{
|
|
||||||
&hostPathPlugin{
|
|
||||||
host: nil,
|
|
||||||
newRecyclerFunc: recyclerFunc,
|
|
||||||
newProvisionerFunc: newProvisioner,
|
|
||||||
config: volumeConfig,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostPathPlugin struct {
|
type hostPathPlugin struct {
|
||||||
host volume.VolumeHost
|
host volume.VolumeHost
|
||||||
// decouple creating Recyclers/Deleters/Provisioners by deferring to a function. Allows for easier testing.
|
// decouple creating Recyclers/Deleters/Provisioners by deferring to a function. Allows for easier testing.
|
||||||
@ -132,6 +121,9 @@ func (plugin *hostPathPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *hostPathPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
func (plugin *hostPathPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||||
|
if !plugin.config.ProvisioningEnabled {
|
||||||
|
return nil, fmt.Errorf("Provisioning in volume plugin %q is disabled", plugin.GetPluginName())
|
||||||
|
}
|
||||||
if len(options.AccessModes) == 0 {
|
if len(options.AccessModes) == 0 {
|
||||||
options.AccessModes = plugin.GetAccessModes()
|
options.AccessModes = plugin.GetAccessModes()
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,8 @@ func TestProvisioner(t *testing.T) {
|
|||||||
err := os.MkdirAll(tempPath, 0750)
|
err := os.MkdirAll(tempPath, 0750)
|
||||||
|
|
||||||
plugMgr := volume.VolumePluginMgr{}
|
plugMgr := volume.VolumePluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
|
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{ProvisioningEnabled: true}),
|
||||||
|
volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
|
||||||
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: tempPath}}}}}
|
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: tempPath}}}}}
|
||||||
plug, err := plugMgr.FindCreatablePluginBySpec(spec)
|
plug, err := plugMgr.FindCreatablePluginBySpec(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||||
"k8s.io/kubernetes/pkg/types"
|
"k8s.io/kubernetes/pkg/types"
|
||||||
@ -55,6 +56,10 @@ type VolumeOptions struct {
|
|||||||
ClusterName string
|
ClusterName string
|
||||||
// Tags to attach to the real volume in the cloud provider - e.g. AWS EBS
|
// Tags to attach to the real volume in the cloud provider - e.g. AWS EBS
|
||||||
CloudTags *map[string]string
|
CloudTags *map[string]string
|
||||||
|
// Volume provisioning parameters from StorageClass
|
||||||
|
Parameters map[string]string
|
||||||
|
// Volume selector from PersistentVolumeClaim
|
||||||
|
Selector *unversioned.LabelSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
// VolumePlugin is an interface to volume plugins that can be used on a
|
// VolumePlugin is an interface to volume plugins that can be used on a
|
||||||
@ -293,6 +298,10 @@ type VolumeConfig struct {
|
|||||||
// the system and only understood by the binary hosting the plugin and the
|
// the system and only understood by the binary hosting the plugin and the
|
||||||
// plugin itself.
|
// plugin itself.
|
||||||
OtherAttributes map[string]string
|
OtherAttributes map[string]string
|
||||||
|
|
||||||
|
// ProvisioningEnabled configures whether provisioning of this plugin is
|
||||||
|
// enabled or not. Currently used only in host_path plugin.
|
||||||
|
ProvisioningEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSpecFromVolume creates an Spec from an api.Volume
|
// NewSpecFromVolume creates an Spec from an api.Volume
|
||||||
@ -429,7 +438,20 @@ func (pm *VolumePluginMgr) FindRecyclablePluginBySpec(spec *Spec) (RecyclableVol
|
|||||||
return nil, fmt.Errorf("no recyclable volume plugin matched")
|
return nil, fmt.Errorf("no recyclable volume plugin matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindDeletablePluginByName fetches a persistent volume plugin by name. If
|
// FindProvisionablePluginByName fetches a persistent volume plugin by name. If
|
||||||
|
// no plugin is found, returns error.
|
||||||
|
func (pm *VolumePluginMgr) FindProvisionablePluginByName(name string) (ProvisionableVolumePlugin, error) {
|
||||||
|
volumePlugin, err := pm.FindPluginByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok {
|
||||||
|
return provisionableVolumePlugin, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no provisionable volume plugin matched")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindDeletablePluginBySppec fetches a persistent volume plugin by spec. If
|
||||||
// no plugin is found, returns error.
|
// no plugin is found, returns error.
|
||||||
func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolumePlugin, error) {
|
func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolumePlugin, error) {
|
||||||
volumePlugin, err := pm.FindPluginBySpec(spec)
|
volumePlugin, err := pm.FindPluginBySpec(spec)
|
||||||
@ -442,6 +464,19 @@ func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolum
|
|||||||
return nil, fmt.Errorf("no deletable volume plugin matched")
|
return nil, fmt.Errorf("no deletable volume plugin matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindDeletablePluginByName fetches a persistent volume plugin by name. If
|
||||||
|
// no plugin is found, returns error.
|
||||||
|
func (pm *VolumePluginMgr) FindDeletablePluginByName(name string) (DeletableVolumePlugin, error) {
|
||||||
|
volumePlugin, err := pm.FindPluginByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok {
|
||||||
|
return deletableVolumePlugin, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no deletable volume plugin matched")
|
||||||
|
}
|
||||||
|
|
||||||
// FindCreatablePluginBySpec fetches a persistent volume plugin by name. If
|
// FindCreatablePluginBySpec fetches a persistent volume plugin by name. If
|
||||||
// no plugin is found, returns error.
|
// no plugin is found, returns error.
|
||||||
func (pm *VolumePluginMgr) FindCreatablePluginBySpec(spec *Spec) (ProvisionableVolumePlugin, error) {
|
func (pm *VolumePluginMgr) FindCreatablePluginBySpec(spec *Spec) (ProvisionableVolumePlugin, error) {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
@ -52,8 +53,15 @@ var _ = framework.KubeDescribe("Dynamic provisioning", func() {
|
|||||||
framework.KubeDescribe("DynamicProvisioner", func() {
|
framework.KubeDescribe("DynamicProvisioner", func() {
|
||||||
It("should create and delete persistent volumes", func() {
|
It("should create and delete persistent volumes", func() {
|
||||||
framework.SkipUnlessProviderIs("openstack", "gce", "aws", "gke")
|
framework.SkipUnlessProviderIs("openstack", "gce", "aws", "gke")
|
||||||
|
|
||||||
|
By("creating a StorageClass")
|
||||||
|
class := newStorageClass()
|
||||||
|
_, err := c.Extensions().StorageClasses().Create(class)
|
||||||
|
defer c.Extensions().StorageClasses().Delete(class.Name)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
By("creating a claim with a dynamic provisioning annotation")
|
By("creating a claim with a dynamic provisioning annotation")
|
||||||
claim := createClaim(ns)
|
claim := newClaim(ns)
|
||||||
defer func() {
|
defer func() {
|
||||||
c.PersistentVolumeClaims(ns).Delete(claim.Name)
|
c.PersistentVolumeClaims(ns).Delete(claim.Name)
|
||||||
}()
|
}()
|
||||||
@ -124,13 +132,13 @@ var _ = framework.KubeDescribe("Dynamic provisioning", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func createClaim(ns string) *api.PersistentVolumeClaim {
|
func newClaim(ns string) *api.PersistentVolumeClaim {
|
||||||
return &api.PersistentVolumeClaim{
|
return &api.PersistentVolumeClaim{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
GenerateName: "pvc-",
|
GenerateName: "pvc-",
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"volume.alpha.kubernetes.io/storage-class": "",
|
"volume.beta.kubernetes.io/storage-class": "fast",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: api.PersistentVolumeClaimSpec{
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
@ -192,3 +200,26 @@ func runInPodWithVolume(c *client.Client, ns, claimName, command string) {
|
|||||||
framework.ExpectNoError(err, "Failed to create pod: %v", err)
|
framework.ExpectNoError(err, "Failed to create pod: %v", err)
|
||||||
framework.ExpectNoError(framework.WaitForPodSuccessInNamespaceSlow(c, pod.Name, pod.Spec.Containers[0].Name, pod.Namespace))
|
framework.ExpectNoError(framework.WaitForPodSuccessInNamespaceSlow(c, pod.Name, pod.Spec.Containers[0].Name, pod.Namespace))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newStorageClass() *extensions.StorageClass {
|
||||||
|
var pluginName string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case framework.ProviderIs("gke"), framework.ProviderIs("gce"):
|
||||||
|
pluginName = "kubernetes.io/gce-pd"
|
||||||
|
case framework.ProviderIs("aws"):
|
||||||
|
pluginName = "kubernetes.io/aws-ebs"
|
||||||
|
case framework.ProviderIs("openstack"):
|
||||||
|
pluginName = "kubernetes.io/cinder"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.StorageClass{
|
||||||
|
TypeMeta: unversioned.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "fast",
|
||||||
|
},
|
||||||
|
Provisioner: pluginName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
fake_cloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
|
fake_cloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
|
||||||
@ -62,6 +63,8 @@ func init() {
|
|||||||
const defaultObjectCount = 100
|
const defaultObjectCount = 100
|
||||||
const defaultSyncPeriod = 10 * time.Second
|
const defaultSyncPeriod = 10 * time.Second
|
||||||
|
|
||||||
|
const provisionerPluginName = "kubernetes.io/mock-provisioner"
|
||||||
|
|
||||||
func getObjectCount() int {
|
func getObjectCount() int {
|
||||||
objectCount := defaultObjectCount
|
objectCount := defaultObjectCount
|
||||||
if s := os.Getenv("KUBE_INTEGRATION_PV_OBJECTS"); s != "" {
|
if s := os.Getenv("KUBE_INTEGRATION_PV_OBJECTS"); s != "" {
|
||||||
@ -849,8 +852,20 @@ func TestPersistentVolumeProvisionMultiPVCs(t *testing.T) {
|
|||||||
defer watchPVC.Stop()
|
defer watchPVC.Stop()
|
||||||
|
|
||||||
// NOTE: This test cannot run in parallel, because it is creating and deleting
|
// NOTE: This test cannot run in parallel, because it is creating and deleting
|
||||||
// non-namespaced objects (PersistenceVolumes).
|
// non-namespaced objects (PersistenceVolumes and StorageClasses).
|
||||||
defer testClient.Core().PersistentVolumes().DeleteCollection(nil, api.ListOptions{})
|
defer testClient.Core().PersistentVolumes().DeleteCollection(nil, api.ListOptions{})
|
||||||
|
defer testClient.Extensions().StorageClasses().DeleteCollection(nil, api.ListOptions{})
|
||||||
|
|
||||||
|
storageClass := extensions.StorageClass{
|
||||||
|
TypeMeta: unversioned.TypeMeta{
|
||||||
|
Kind: "StorageClass",
|
||||||
|
},
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "gold",
|
||||||
|
},
|
||||||
|
Provisioner: provisionerPluginName,
|
||||||
|
}
|
||||||
|
testClient.Extensions().StorageClasses().Create(&storageClass)
|
||||||
|
|
||||||
binder.Run()
|
binder.Run()
|
||||||
defer binder.Stop()
|
defer binder.Stop()
|
||||||
@ -860,7 +875,7 @@ func TestPersistentVolumeProvisionMultiPVCs(t *testing.T) {
|
|||||||
for i := 0; i < objCount; i++ {
|
for i := 0; i < objCount; i++ {
|
||||||
pvc := createPVC("pvc-provision-"+strconv.Itoa(i), ns.Name, "1G", []api.PersistentVolumeAccessMode{api.ReadWriteOnce})
|
pvc := createPVC("pvc-provision-"+strconv.Itoa(i), ns.Name, "1G", []api.PersistentVolumeAccessMode{api.ReadWriteOnce})
|
||||||
pvc.Annotations = map[string]string{
|
pvc.Annotations = map[string]string{
|
||||||
"volume.alpha.kubernetes.io/storage-class": "",
|
"volume.beta.kubernetes.io/storage-class": "gold",
|
||||||
}
|
}
|
||||||
pvcs[i] = pvc
|
pvcs[i] = pvc
|
||||||
}
|
}
|
||||||
@ -1086,7 +1101,7 @@ func createClients(ns *api.Namespace, t *testing.T, s *httptest.Server, syncPeri
|
|||||||
|
|
||||||
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)
|
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)
|
||||||
plugin := &volumetest.FakeVolumePlugin{
|
plugin := &volumetest.FakeVolumePlugin{
|
||||||
PluginName: "plugin-name",
|
PluginName: provisionerPluginName,
|
||||||
Host: host,
|
Host: host,
|
||||||
Config: volume.VolumeConfig{},
|
Config: volume.VolumeConfig{},
|
||||||
LastProvisionerOptions: volume.VolumeOptions{},
|
LastProvisionerOptions: volume.VolumeOptions{},
|
||||||
@ -1101,7 +1116,7 @@ func createClients(ns *api.Namespace, t *testing.T, s *httptest.Server, syncPeri
|
|||||||
cloud := &fake_cloud.FakeCloud{}
|
cloud := &fake_cloud.FakeCloud{}
|
||||||
|
|
||||||
syncPeriod = getSyncPeriod(syncPeriod)
|
syncPeriod = getSyncPeriod(syncPeriod)
|
||||||
ctrl := persistentvolumecontroller.NewPersistentVolumeController(binderClient, syncPeriod, plugin, plugins, cloud, "", nil, nil, nil, true)
|
ctrl := persistentvolumecontroller.NewPersistentVolumeController(binderClient, syncPeriod, plugins, cloud, "", nil, nil, nil, nil, true)
|
||||||
|
|
||||||
watchPV, err := testClient.PersistentVolumes().Watch(api.ListOptions{})
|
watchPV, err := testClient.PersistentVolumes().Watch(api.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user