remove storage capability auto detection
Signed-off-by: dkven <dkvvven@gmail.com>
This commit is contained in:
@@ -140,7 +140,6 @@ func addControllers(
|
|||||||
capability.SnapshotSupported(client.Kubernetes().Discovery()),
|
capability.SnapshotSupported(client.Kubernetes().Discovery()),
|
||||||
client.Snapshot().SnapshotV1beta1().VolumeSnapshotClasses(),
|
client.Snapshot().SnapshotV1beta1().VolumeSnapshotClasses(),
|
||||||
informerFactory.SnapshotSharedInformerFactory().Snapshot().V1beta1().VolumeSnapshotClasses(),
|
informerFactory.SnapshotSharedInformerFactory().Snapshot().V1beta1().VolumeSnapshotClasses(),
|
||||||
kubernetesInformer.Storage().V1beta1().CSIDrivers(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
volumeExpansionController := expansion.NewVolumeExpansionController(
|
volumeExpansionController := expansion.NewVolumeExpansionController(
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: storage.kubesphere.io/v1alpha1
|
||||||
|
kind: ProvisionerCapability
|
||||||
|
metadata:
|
||||||
|
name: disk-csi-qingcloud-com
|
||||||
|
spec:
|
||||||
|
pluginInfo:
|
||||||
|
name: disk.csi.qingcloud.com
|
||||||
|
version: ""
|
||||||
|
features:
|
||||||
|
topology: true
|
||||||
|
snapshot:
|
||||||
|
create: true
|
||||||
|
list: false
|
||||||
|
volume:
|
||||||
|
attach: true
|
||||||
|
clone: true
|
||||||
|
create: true
|
||||||
|
expandMode: OFFLINE
|
||||||
|
list: false
|
||||||
|
stats: true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: storage.kubesphere.io/v1alpha1
|
||||||
|
kind: ProvisionerCapability
|
||||||
|
metadata:
|
||||||
|
name: neonsan-csi-qingstor-com
|
||||||
|
spec:
|
||||||
|
pluginInfo:
|
||||||
|
name: neonsan.csi.qingstor.com
|
||||||
|
version: ""
|
||||||
|
features:
|
||||||
|
topology: false
|
||||||
|
snapshot:
|
||||||
|
create: true
|
||||||
|
list: false
|
||||||
|
volume:
|
||||||
|
attach: true
|
||||||
|
clone: true
|
||||||
|
create: true
|
||||||
|
expandMode: OFFLINE
|
||||||
|
list: false
|
||||||
|
stats: true
|
||||||
1
go.mod
1
go.mod
@@ -15,7 +15,6 @@ require (
|
|||||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
|
||||||
github.com/aws/aws-sdk-go v1.33.12
|
github.com/aws/aws-sdk-go v1.33.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/container-storage-interface/spec v1.2.0
|
|
||||||
github.com/containernetworking/cni v0.8.0
|
github.com/containernetworking/cni v0.8.0
|
||||||
github.com/coreos/go-oidc v2.1.0+incompatible
|
github.com/coreos/go-oidc v2.1.0+incompatible
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
|||||||
@@ -35,42 +35,36 @@ import (
|
|||||||
snapinformers "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions/volumesnapshot/v1beta1"
|
snapinformers "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions/volumesnapshot/v1beta1"
|
||||||
snapshotlisters "github.com/kubernetes-csi/external-snapshotter/client/v3/listers/volumesnapshot/v1beta1"
|
snapshotlisters "github.com/kubernetes-csi/external-snapshotter/client/v3/listers/volumesnapshot/v1beta1"
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
storagev1 "k8s.io/api/storage/v1"
|
||||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
storageinformersv1 "k8s.io/client-go/informers/storage/v1"
|
storageinformersv1 "k8s.io/client-go/informers/storage/v1"
|
||||||
storageinformersv1beta1 "k8s.io/client-go/informers/storage/v1beta1"
|
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
storageclient "k8s.io/client-go/kubernetes/typed/storage/v1"
|
storageclient "k8s.io/client-go/kubernetes/typed/storage/v1"
|
||||||
storagelistersv1 "k8s.io/client-go/listers/storage/v1"
|
storagelistersv1 "k8s.io/client-go/listers/storage/v1"
|
||||||
storagelistersv1beta1 "k8s.io/client-go/listers/storage/v1beta1"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
capability "kubesphere.io/kubesphere/pkg/apis/storage/v1alpha1"
|
ksstorage "kubesphere.io/kubesphere/pkg/apis/storage/v1alpha1"
|
||||||
crdscheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
crdscheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||||
capabilityclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/storage/v1alpha1"
|
ksstorageclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/storage/v1alpha1"
|
||||||
capabilityinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/storage/v1alpha1"
|
ksstorageinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/storage/v1alpha1"
|
||||||
capabilitylisters "kubesphere.io/kubesphere/pkg/client/listers/storage/v1alpha1"
|
ksstoragelisters "kubesphere.io/kubesphere/pkg/client/listers/storage/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minSnapshotSupportedVersion = "v1.17.0"
|
minSnapshotSupportedVersion = "v1.17.0"
|
||||||
csiAddressFormat = "/var/lib/kubelet/plugins/%s/csi.sock"
|
|
||||||
annotationSupportSnapshot = "storageclass.kubesphere.io/support-snapshot"
|
annotationSupportSnapshot = "storageclass.kubesphere.io/support-snapshot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type csiAddressGetter func(storageClassProvisioner string) string
|
|
||||||
|
|
||||||
type StorageCapabilityController struct {
|
type StorageCapabilityController struct {
|
||||||
storageClassCapabilityClient capabilityclient.StorageClassCapabilityInterface
|
storageClassCapabilityClient ksstorageclient.StorageClassCapabilityInterface
|
||||||
storageCapabilityLister capabilitylisters.StorageClassCapabilityLister
|
storageClassCapabilityLister ksstoragelisters.StorageClassCapabilityLister
|
||||||
storageClassCapabilitySynced cache.InformerSynced
|
storageClassCapabilitySynced cache.InformerSynced
|
||||||
|
|
||||||
provisionerCapabilityLister capabilitylisters.ProvisionerCapabilityLister
|
provisionerCapabilityLister ksstoragelisters.ProvisionerCapabilityLister
|
||||||
provisionerCapabilitySynced cache.InformerSynced
|
provisionerCapabilitySynced cache.InformerSynced
|
||||||
|
|
||||||
storageClassClient storageclient.StorageClassInterface
|
storageClassClient storageclient.StorageClassInterface
|
||||||
@@ -82,42 +76,33 @@ type StorageCapabilityController struct {
|
|||||||
snapshotClassLister snapshotlisters.VolumeSnapshotClassLister
|
snapshotClassLister snapshotlisters.VolumeSnapshotClassLister
|
||||||
snapshotClassSynced cache.InformerSynced
|
snapshotClassSynced cache.InformerSynced
|
||||||
|
|
||||||
csiDriverLister storagelistersv1beta1.CSIDriverLister
|
|
||||||
csiDriverSynced cache.InformerSynced
|
|
||||||
|
|
||||||
csiAddressGetter csiAddressGetter
|
|
||||||
|
|
||||||
workQueue workqueue.RateLimitingInterface
|
workQueue workqueue.RateLimitingInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// This controller is responsible to watch StorageClass, SnapshotClass.
|
// This controller is responsible to watch StorageClass/ProvisionerCapability.
|
||||||
// And then update StorageClassCapability CRD resource object to the newest status.
|
// And then update StorageClassCapability CRD resource object to the newest status.
|
||||||
func NewController(
|
func NewController(
|
||||||
capabilityClient capabilityclient.StorageClassCapabilityInterface,
|
storageClassCapabilityClient ksstorageclient.StorageClassCapabilityInterface,
|
||||||
capabilityInformer capabilityinformers.Interface,
|
ksStorageInformer ksstorageinformers.Interface,
|
||||||
storageClassClient storageclient.StorageClassInterface,
|
storageClassClient storageclient.StorageClassInterface,
|
||||||
storageClassInformer storageinformersv1.StorageClassInformer,
|
storageClassInformer storageinformersv1.StorageClassInformer,
|
||||||
snapshotSupported bool,
|
snapshotSupported bool,
|
||||||
snapshotClassClient snapshotclient.VolumeSnapshotClassInterface,
|
snapshotClassClient snapshotclient.VolumeSnapshotClassInterface,
|
||||||
snapshotClassInformer snapinformers.VolumeSnapshotClassInformer,
|
snapshotClassInformer snapinformers.VolumeSnapshotClassInformer,
|
||||||
csiDriverInformer storageinformersv1beta1.CSIDriverInformer,
|
|
||||||
) *StorageCapabilityController {
|
) *StorageCapabilityController {
|
||||||
|
|
||||||
utilruntime.Must(crdscheme.AddToScheme(scheme.Scheme))
|
utilruntime.Must(crdscheme.AddToScheme(scheme.Scheme))
|
||||||
|
|
||||||
controller := &StorageCapabilityController{
|
controller := &StorageCapabilityController{
|
||||||
storageClassCapabilityClient: capabilityClient,
|
storageClassCapabilityClient: storageClassCapabilityClient,
|
||||||
storageCapabilityLister: capabilityInformer.StorageClassCapabilities().Lister(),
|
storageClassCapabilityLister: ksStorageInformer.StorageClassCapabilities().Lister(),
|
||||||
storageClassCapabilitySynced: capabilityInformer.StorageClassCapabilities().Informer().HasSynced,
|
storageClassCapabilitySynced: ksStorageInformer.StorageClassCapabilities().Informer().HasSynced,
|
||||||
provisionerCapabilityLister: capabilityInformer.ProvisionerCapabilities().Lister(),
|
provisionerCapabilityLister: ksStorageInformer.ProvisionerCapabilities().Lister(),
|
||||||
provisionerCapabilitySynced: capabilityInformer.ProvisionerCapabilities().Informer().HasSynced,
|
provisionerCapabilitySynced: ksStorageInformer.ProvisionerCapabilities().Informer().HasSynced,
|
||||||
storageClassClient: storageClassClient,
|
storageClassClient: storageClassClient,
|
||||||
storageClassLister: storageClassInformer.Lister(),
|
storageClassLister: storageClassInformer.Lister(),
|
||||||
storageClassSynced: storageClassInformer.Informer().HasSynced,
|
storageClassSynced: storageClassInformer.Informer().HasSynced,
|
||||||
snapshotSupported: snapshotSupported,
|
snapshotSupported: snapshotSupported,
|
||||||
csiDriverLister: csiDriverInformer.Lister(),
|
|
||||||
csiDriverSynced: csiDriverInformer.Informer().HasSynced,
|
|
||||||
csiAddressGetter: csiAddress,
|
|
||||||
workQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "StorageClasses"),
|
workQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "StorageClasses"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +125,24 @@ func NewController(
|
|||||||
DeleteFunc: controller.enqueueStorageClass,
|
DeleteFunc: controller.enqueueStorageClass,
|
||||||
})
|
})
|
||||||
|
|
||||||
csiDriverInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
// ProvisionerCapability acts as a value source of its relevant StorageClassCapabilities
|
||||||
AddFunc: controller.handlerCSIDriver,
|
// so when a PC is created/updated, the corresponding SCCs should be created(if not exists)/updated
|
||||||
|
// we achive this by simply enqueueing the StorageClasses of the same provisioner
|
||||||
|
// but don't overdo by cascade deleting the SCCs when a PC is deleted
|
||||||
|
// since the role of PCs is more like a template rather than owner to SCCs
|
||||||
|
|
||||||
|
// This is a backward compatible fix to remove the useless auto detection of SCCs
|
||||||
|
// in the future, we will only keep ProvisionerCapability and remove the StorageClassCapability CRD entirely
|
||||||
|
ksStorageInformer.ProvisionerCapabilities().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: controller.handleProvisionerCapability,
|
||||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||||
return
|
newPC := newObj.(*ksstorage.ProvisionerCapability)
|
||||||
|
oldPC := oldObj.(*ksstorage.ProvisionerCapability)
|
||||||
|
if newPC.ResourceVersion == oldPC.ResourceVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.handleProvisionerCapability(newObj)
|
||||||
},
|
},
|
||||||
DeleteFunc: controller.handlerCSIDriver,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
@@ -165,7 +162,6 @@ func (c *StorageCapabilityController) Run(threadCnt int, stopCh <-chan struct{})
|
|||||||
c.storageClassCapabilitySynced,
|
c.storageClassCapabilitySynced,
|
||||||
c.provisionerCapabilitySynced,
|
c.provisionerCapabilitySynced,
|
||||||
c.storageClassSynced,
|
c.storageClassSynced,
|
||||||
c.csiDriverSynced,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.snapshotAllowed() {
|
if c.snapshotAllowed() {
|
||||||
@@ -185,16 +181,16 @@ func (c *StorageCapabilityController) Run(threadCnt int, stopCh <-chan struct{})
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StorageCapabilityController) handlerCSIDriver(obj interface{}) {
|
func (c *StorageCapabilityController) handleProvisionerCapability(obj interface{}) {
|
||||||
csiDriver := obj.(*storagev1beta1.CSIDriver)
|
provisionerCapability := obj.(*ksstorage.ProvisionerCapability)
|
||||||
storageClasses, err := c.storageClassLister.List(labels.Everything())
|
storageClasses, err := c.storageClassLister.List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error("list StorageClass error when handler csiDriver", err)
|
klog.Error("list StorageClass error when handle provisionerCapability", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, storageClass := range storageClasses {
|
for _, storageClass := range storageClasses {
|
||||||
if storageClass.Provisioner == csiDriver.Name {
|
if getProvisionerCapabilityName(storageClass.Provisioner) == provisionerCapability.Name {
|
||||||
klog.V(4).Infof("enqueue StorageClass %s when handling csiDriver", storageClass.Name)
|
klog.V(4).Infof("enqueue StorageClass %s while handling provisionerCapability", storageClass.Name)
|
||||||
c.enqueueStorageClass(storageClass)
|
c.enqueueStorageClass(storageClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,14 +273,16 @@ func (c *StorageCapabilityController) syncHandler(key string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// No capability because csi-plugin not installed
|
// The corresponding ProvisionerCapability Object does not exist
|
||||||
if capabilitySpec == nil {
|
if capabilitySpec == nil {
|
||||||
klog.Infof("StorageClass %s has no capability", name)
|
klog.Infof("Can't get StorageClass %s's capability", name)
|
||||||
err = c.updateStorageClassSnapshotSupported(storageClass, false)
|
err = c.updateStorageClassSnapshotSupported(storageClass, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.deleteStorageCapability(name)
|
// Don't delete the already created SCC
|
||||||
|
// as it might be created manually by user
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
klog.Infof("StorageClass %s has capability %v", name, capabilitySpec)
|
klog.Infof("StorageClass %s has capability %v", name, capabilitySpec)
|
||||||
|
|
||||||
@@ -315,11 +313,11 @@ func (c *StorageCapabilityController) syncHandler(key string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle StorageClassCapability with the same name of StorageClass
|
// Handle StorageClassCapability with the same name of StorageClass
|
||||||
storageClassCapabilityExist, err := c.storageCapabilityLister.Get(storageClass.Name)
|
storageClassCapabilityExist, err := c.storageClassCapabilityLister.Get(storageClass.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
// If StorageClassCapability doesn't exist, create it
|
// If StorageClassCapability doesn't exist, create it
|
||||||
storageClassCapabilityCreate := &capability.StorageClassCapability{ObjectMeta: metav1.ObjectMeta{Name: storageClass.Name}}
|
storageClassCapabilityCreate := &ksstorage.StorageClassCapability{ObjectMeta: metav1.ObjectMeta{Name: storageClass.Name}}
|
||||||
storageClassCapabilityCreate.Spec = *capabilitySpec
|
storageClassCapabilityCreate.Spec = *capabilitySpec
|
||||||
klog.Info("Create StorageClassCapability: ", storageClassCapabilityCreate)
|
klog.Info("Create StorageClassCapability: ", storageClassCapabilityCreate)
|
||||||
_, err = c.storageClassCapabilityClient.Create(context.Background(), storageClassCapabilityCreate, metav1.CreateOptions{})
|
_, err = c.storageClassCapabilityClient.Create(context.Background(), storageClassCapabilityCreate, metav1.CreateOptions{})
|
||||||
@@ -355,7 +353,7 @@ func (c *StorageCapabilityController) updateStorageClassSnapshotSupported(storag
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *StorageCapabilityController) deleteStorageCapability(name string) error {
|
func (c *StorageCapabilityController) deleteStorageCapability(name string) error {
|
||||||
_, err := c.storageCapabilityLister.Get(name)
|
_, err := c.storageClassCapabilityLister.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
@@ -381,7 +379,7 @@ func (c *StorageCapabilityController) deleteSnapshotClass(name string) error {
|
|||||||
return c.snapshotClassClient.Delete(context.Background(), name, metav1.DeleteOptions{})
|
return c.snapshotClassClient.Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StorageCapabilityController) capabilityFromProvisioner(provisioner string) (*capability.StorageClassCapabilitySpec, error) {
|
func (c *StorageCapabilityController) capabilityFromProvisioner(provisioner string) (*ksstorage.StorageClassCapabilitySpec, error) {
|
||||||
provisionerCapability, err := c.provisionerCapabilityLister.Get(getProvisionerCapabilityName(provisioner))
|
provisionerCapability, err := c.provisionerCapabilityLister.Get(getProvisionerCapabilityName(provisioner))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
@@ -390,13 +388,13 @@ func (c *StorageCapabilityController) capabilityFromProvisioner(provisioner stri
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
klog.V(4).Infof("get provisioner capability:%s %s", provisioner, provisionerCapability.Name)
|
klog.V(4).Infof("get provisioner capability:%s %s", provisioner, provisionerCapability.Name)
|
||||||
capabilitySpec := &capability.StorageClassCapabilitySpec{
|
capabilitySpec := &ksstorage.StorageClassCapabilitySpec{
|
||||||
Features: provisionerCapability.Spec.Features,
|
Features: provisionerCapability.Spec.Features,
|
||||||
}
|
}
|
||||||
return capabilitySpec, nil
|
return capabilitySpec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StorageCapabilityController) getCapabilitySpec(storageClass *storagev1.StorageClass) (*capability.StorageClassCapabilitySpec, error) {
|
func (c *StorageCapabilityController) getCapabilitySpec(storageClass *storagev1.StorageClass) (*ksstorage.StorageClassCapabilitySpec, error) {
|
||||||
// get from provisioner capability first
|
// get from provisioner capability first
|
||||||
klog.V(4).Info("get cap ", storageClass.Provisioner)
|
klog.V(4).Info("get cap ", storageClass.Provisioner)
|
||||||
capabilitySpec, err := c.capabilityFromProvisioner(storageClass.Provisioner)
|
capabilitySpec, err := c.capabilityFromProvisioner(storageClass.Provisioner)
|
||||||
@@ -404,24 +402,10 @@ func (c *StorageCapabilityController) getCapabilitySpec(storageClass *storagev1.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// csi of storage capability
|
|
||||||
if capabilitySpec == nil {
|
|
||||||
isCsi, err := c.isCSIStorage(storageClass.Provisioner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isCsi {
|
|
||||||
capabilitySpec, err = csiCapability(c.csiAddressGetter(storageClass.Provisioner))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if capabilitySpec != nil {
|
if capabilitySpec != nil {
|
||||||
capabilitySpec.Provisioner = storageClass.Provisioner
|
capabilitySpec.Provisioner = storageClass.Provisioner
|
||||||
if storageClass.AllowVolumeExpansion == nil || !*storageClass.AllowVolumeExpansion {
|
if storageClass.AllowVolumeExpansion == nil || !*storageClass.AllowVolumeExpansion {
|
||||||
capabilitySpec.Features.Volume.Expand = capability.ExpandModeUnknown
|
capabilitySpec.Features.Volume.Expand = ksstorage.ExpandModeUnknown
|
||||||
}
|
}
|
||||||
if !c.snapshotSupported {
|
if !c.snapshotSupported {
|
||||||
capabilitySpec.Features.Snapshot.Create = false
|
capabilitySpec.Features.Snapshot.Create = false
|
||||||
@@ -431,22 +415,6 @@ func (c *StorageCapabilityController) getCapabilitySpec(storageClass *storagev1.
|
|||||||
return capabilitySpec, nil
|
return capabilitySpec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StorageCapabilityController) isCSIStorage(provisioner string) (bool, error) {
|
|
||||||
_, err := c.csiDriverLister.Get(provisioner)
|
|
||||||
if err != nil {
|
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is used for test of CSIDriver on windows
|
|
||||||
func (c *StorageCapabilityController) setCSIAddressGetter(getter csiAddressGetter) {
|
|
||||||
c.csiAddressGetter = getter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StorageCapabilityController) snapshotAllowed() bool {
|
func (c *StorageCapabilityController) snapshotAllowed() bool {
|
||||||
return c.snapshotSupported && c.snapshotClassClient != nil && c.snapshotClassLister != nil && c.snapshotClassSynced != nil
|
return c.snapshotSupported && c.snapshotClassClient != nil && c.snapshotClassLister != nil && c.snapshotClassSynced != nil
|
||||||
}
|
}
|
||||||
@@ -464,10 +432,6 @@ func SnapshotSupported(discoveryInterface discovery.DiscoveryInterface) bool {
|
|||||||
return ver.AtLeast(minVer)
|
return ver.AtLeast(minVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiAddress(provisioner string) string {
|
|
||||||
return fmt.Sprintf(csiAddressFormat, provisioner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProvisionerCapabilityName(provisioner string) string {
|
func getProvisionerCapabilityName(provisioner string) string {
|
||||||
return strings.NewReplacer(".", "-", "/", "-").Replace(provisioner)
|
return strings.NewReplacer(".", "-", "/", "-").Replace(provisioner)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,8 @@
|
|||||||
package capability
|
package capability
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
//"github.com/google/go-cmp/cmp"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -32,7 +29,6 @@ import (
|
|||||||
snapfake "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned/fake"
|
snapfake "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned/fake"
|
||||||
snapinformers "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions"
|
snapinformers "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions"
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
storagev1 "k8s.io/api/storage/v1"
|
||||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -59,7 +55,7 @@ type fixture struct {
|
|||||||
snapshotClassClient *snapfake.Clientset
|
snapshotClassClient *snapfake.Clientset
|
||||||
ksClient *ksfake.Clientset
|
ksClient *ksfake.Clientset
|
||||||
// Objects from here preload into NewSimpleFake.
|
// Objects from here preload into NewSimpleFake.
|
||||||
storageObjects []runtime.Object // include StorageClass and CSIDriver
|
storageObjects []runtime.Object // include StorageClass
|
||||||
snapshotClassObjects []runtime.Object
|
snapshotClassObjects []runtime.Object
|
||||||
capabilityObjects []runtime.Object // include StorageClassCapability and ProvisionerCapability
|
capabilityObjects []runtime.Object // include StorageClassCapability and ProvisionerCapability
|
||||||
// Objects to put in the store.
|
// Objects to put in the store.
|
||||||
@@ -67,19 +63,14 @@ type fixture struct {
|
|||||||
snapshotClassLister []*snapbeta1.VolumeSnapshotClass
|
snapshotClassLister []*snapbeta1.VolumeSnapshotClass
|
||||||
storageClassCapabilityLister []*ksv1alpha1.StorageClassCapability
|
storageClassCapabilityLister []*ksv1alpha1.StorageClassCapability
|
||||||
provisionerCapabilityLister []*ksv1alpha1.ProvisionerCapability
|
provisionerCapabilityLister []*ksv1alpha1.ProvisionerCapability
|
||||||
csiDriverLister []*storagev1beta1.CSIDriver
|
|
||||||
// Actions expected to happen on the client.
|
// Actions expected to happen on the client.
|
||||||
actions []core.Action
|
actions []core.Action
|
||||||
// CSI server
|
|
||||||
runCSIServer bool
|
|
||||||
fakeCSIServer *fakeCSIServer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFixture(t *testing.T, snapshotSupported bool, runCSIServer bool) *fixture {
|
func newFixture(t *testing.T, snapshotSupported bool) *fixture {
|
||||||
return &fixture{
|
return &fixture{
|
||||||
t: t,
|
t: t,
|
||||||
snapshotSupported: snapshotSupported,
|
snapshotSupported: snapshotSupported,
|
||||||
runCSIServer: runCSIServer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,21 +95,11 @@ func (f *fixture) newController() (*StorageCapabilityController,
|
|||||||
f.snapshotSupported,
|
f.snapshotSupported,
|
||||||
f.snapshotClassClient.SnapshotV1beta1().VolumeSnapshotClasses(),
|
f.snapshotClassClient.SnapshotV1beta1().VolumeSnapshotClasses(),
|
||||||
snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses(),
|
snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses(),
|
||||||
k8sInformers.Storage().V1beta1().CSIDrivers(),
|
|
||||||
)
|
)
|
||||||
if f.runCSIServer {
|
|
||||||
port := 30000 + rand.Intn(100)
|
|
||||||
fakeCSIServer, address := newTestCSIServer(port)
|
|
||||||
f.fakeCSIServer = fakeCSIServer
|
|
||||||
c.setCSIAddressGetter(func(storageClassProvisioner string) string { return address })
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, storageClass := range f.storageClassLister {
|
for _, storageClass := range f.storageClassLister {
|
||||||
_ = k8sInformers.Storage().V1().StorageClasses().Informer().GetIndexer().Add(storageClass)
|
_ = k8sInformers.Storage().V1().StorageClasses().Informer().GetIndexer().Add(storageClass)
|
||||||
}
|
}
|
||||||
for _, csiDriver := range f.csiDriverLister {
|
|
||||||
_ = k8sInformers.Storage().V1beta1().CSIDrivers().Informer().GetIndexer().Add(csiDriver)
|
|
||||||
}
|
|
||||||
for _, snapshotClass := range f.snapshotClassLister {
|
for _, snapshotClass := range f.snapshotClassLister {
|
||||||
_ = snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses().Informer().GetIndexer().Add(snapshotClass)
|
_ = snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses().Informer().GetIndexer().Add(snapshotClass)
|
||||||
}
|
}
|
||||||
@@ -135,11 +116,6 @@ func (f *fixture) newController() (*StorageCapabilityController,
|
|||||||
func (f *fixture) runController(scName string, startInformers bool, expectError bool) {
|
func (f *fixture) runController(scName string, startInformers bool, expectError bool) {
|
||||||
c, k8sI, crdI, snapI := f.newController()
|
c, k8sI, crdI, snapI := f.newController()
|
||||||
|
|
||||||
if f.runCSIServer {
|
|
||||||
f.fakeCSIServer.run()
|
|
||||||
defer f.fakeCSIServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if startInformers {
|
if startInformers {
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
defer close(stopCh)
|
defer close(stopCh)
|
||||||
@@ -276,6 +252,26 @@ func newStorageClass(name string, provisioner string) *storagev1.StorageClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newStorageClassCapabilitySpec() *ksv1alpha1.StorageClassCapabilitySpec {
|
||||||
|
return &ksv1alpha1.StorageClassCapabilitySpec{
|
||||||
|
Features: ksv1alpha1.CapabilityFeatures{
|
||||||
|
Topology: false,
|
||||||
|
Volume: ksv1alpha1.VolumeFeature{
|
||||||
|
Create: true,
|
||||||
|
Attach: false,
|
||||||
|
List: false,
|
||||||
|
Clone: true,
|
||||||
|
Stats: true,
|
||||||
|
Expand: ksv1alpha1.ExpandModeOffline,
|
||||||
|
},
|
||||||
|
Snapshot: ksv1alpha1.SnapshotFeature{
|
||||||
|
Create: true,
|
||||||
|
List: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newStorageClassCapability(storageClass *storagev1.StorageClass) *ksv1alpha1.StorageClassCapability {
|
func newStorageClassCapability(storageClass *storagev1.StorageClass) *ksv1alpha1.StorageClassCapability {
|
||||||
storageClassCapability := &ksv1alpha1.StorageClassCapability{}
|
storageClassCapability := &ksv1alpha1.StorageClassCapability{}
|
||||||
storageClassCapability.Name = storageClass.Name
|
storageClassCapability.Name = storageClass.Name
|
||||||
@@ -289,17 +285,9 @@ func newProvisionerCapability(storageClass *storagev1.StorageClass) *ksv1alpha1.
|
|||||||
provisionerCapability.Name = getProvisionerCapabilityName(storageClass.Provisioner)
|
provisionerCapability.Name = getProvisionerCapabilityName(storageClass.Provisioner)
|
||||||
provisionerCapability.Spec.PluginInfo.Name = storageClass.Provisioner
|
provisionerCapability.Spec.PluginInfo.Name = storageClass.Provisioner
|
||||||
provisionerCapability.Spec.Features = newStorageClassCapabilitySpec().Features
|
provisionerCapability.Spec.Features = newStorageClassCapabilitySpec().Features
|
||||||
// ProvisionerCapability snapshot is always false
|
|
||||||
provisionerCapability.Spec.Features.Snapshot.Create = false
|
|
||||||
return provisionerCapability
|
return provisionerCapability
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCSIDriver(storageClass *storagev1.StorageClass) *storagev1beta1.CSIDriver {
|
|
||||||
csiDriver := &storagev1beta1.CSIDriver{}
|
|
||||||
csiDriver.Name = storageClass.Provisioner
|
|
||||||
return csiDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSnapshotClass(storageClass *storagev1.StorageClass) *snapbeta1.VolumeSnapshotClass {
|
func newSnapshotClass(storageClass *storagev1.StorageClass) *snapbeta1.VolumeSnapshotClass {
|
||||||
return &snapbeta1.VolumeSnapshotClass{
|
return &snapbeta1.VolumeSnapshotClass{
|
||||||
ObjectMeta: v1.ObjectMeta{
|
ObjectMeta: v1.ObjectMeta{
|
||||||
@@ -320,18 +308,19 @@ func getKey(sc *storagev1.StorageClass, t *testing.T) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateStorageClass(t *testing.T) {
|
func TestCreateStorageClass(t *testing.T) {
|
||||||
fixture := newFixture(t, true, true)
|
fixture := newFixture(t, true)
|
||||||
storageClass := newStorageClass("csi-example", "csi.example.com")
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
storageClassUpdate := storageClass.DeepCopy()
|
storageClassUpdate := storageClass.DeepCopy()
|
||||||
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "true"}
|
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "true"}
|
||||||
|
provisionerCapability := newProvisionerCapability(storageClass)
|
||||||
snapshotClass := newSnapshotClass(storageClass)
|
snapshotClass := newSnapshotClass(storageClass)
|
||||||
storageClassCapability := newStorageClassCapability(storageClass)
|
storageClassCapability := newStorageClassCapability(storageClass)
|
||||||
csiDriver := newCSIDriver(storageClass)
|
|
||||||
|
|
||||||
// Objects exist
|
// Objects exist
|
||||||
fixture.storageObjects = append(fixture.storageObjects, storageClass, csiDriver)
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
||||||
fixture.csiDriverLister = append(fixture.csiDriverLister, csiDriver)
|
fixture.capabilityObjects = append(fixture.capabilityObjects, provisionerCapability)
|
||||||
|
fixture.provisionerCapabilityLister = append(fixture.provisionerCapabilityLister, provisionerCapability)
|
||||||
|
|
||||||
// Action expected
|
// Action expected
|
||||||
fixture.expectCreateSnapshotClassAction(snapshotClass)
|
fixture.expectCreateSnapshotClassAction(snapshotClass)
|
||||||
@@ -342,28 +331,69 @@ func TestCreateStorageClass(t *testing.T) {
|
|||||||
fixture.run(getKey(storageClass, t))
|
fixture.run(getKey(storageClass, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateStorageClassWithoutProvisionerCapability(t *testing.T) {
|
||||||
|
fixture := newFixture(t, true)
|
||||||
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
|
|
||||||
|
// Objects exist
|
||||||
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
|
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
||||||
|
|
||||||
|
storageClassUpdate := storageClass.DeepCopy()
|
||||||
|
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "false"}
|
||||||
|
fixture.expectUpdateStorageClassAction(storageClassUpdate)
|
||||||
|
|
||||||
|
// Run test
|
||||||
|
fixture.run(getKey(storageClass, t))
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpdateStorageClass(t *testing.T) {
|
func TestUpdateStorageClass(t *testing.T) {
|
||||||
storageClass := newStorageClass("csi-example", "csi.example.com")
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
storageClass.Annotations = map[string]string{annotationSupportSnapshot: "true"}
|
storageClass.Annotations = map[string]string{annotationSupportSnapshot: "true"}
|
||||||
snapshotClass := newSnapshotClass(storageClass)
|
snapshotClass := newSnapshotClass(storageClass)
|
||||||
storageClassCapabilityUpdate := newStorageClassCapability(storageClass)
|
storageClassCapabilityUpdate := newStorageClassCapability(storageClass)
|
||||||
storageClassCapability := newStorageClassCapability(storageClass)
|
storageClassCapability := newStorageClassCapability(storageClass)
|
||||||
|
provisionerCapability := newProvisionerCapability(storageClass)
|
||||||
//old and new should have deference
|
//old and new should have deference
|
||||||
storageClassCapability.Spec.Features.Volume.Create = !storageClassCapability.Spec.Features.Volume.Create
|
storageClassCapability.Spec.Features.Volume.Create = !storageClassCapability.Spec.Features.Volume.Create
|
||||||
csiDriver := newCSIDriver(storageClass)
|
|
||||||
|
|
||||||
fixture := newFixture(t, true, true)
|
fixture := newFixture(t, true)
|
||||||
// Object exist
|
// Object exist
|
||||||
fixture.storageObjects = append(fixture.storageObjects, storageClass, csiDriver)
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
|
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
||||||
|
fixture.snapshotClassObjects = append(fixture.snapshotClassObjects, snapshotClass)
|
||||||
|
fixture.snapshotClassLister = append(fixture.snapshotClassLister, snapshotClass)
|
||||||
|
fixture.capabilityObjects = append(fixture.capabilityObjects, storageClassCapability, provisionerCapability)
|
||||||
|
fixture.storageClassCapabilityLister = append(fixture.storageClassCapabilityLister, storageClassCapability)
|
||||||
|
fixture.provisionerCapabilityLister = append(fixture.provisionerCapabilityLister, provisionerCapability)
|
||||||
|
|
||||||
|
// Action expected
|
||||||
|
fixture.expectUpdateStorageClassCapabilitiesAction(storageClassCapabilityUpdate)
|
||||||
|
|
||||||
|
// Run test
|
||||||
|
fixture.run(getKey(storageClass, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateStorageClassWithoutProvisionerCapability(t *testing.T) {
|
||||||
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
|
storageClass.Annotations = map[string]string{annotationSupportSnapshot: "true"}
|
||||||
|
storageClassUpdate := storageClass.DeepCopy()
|
||||||
|
storageClassUpdate.Annotations[annotationSupportSnapshot] = "false"
|
||||||
|
snapshotClass := newSnapshotClass(storageClass)
|
||||||
|
storageClassCapability := newStorageClassCapability(storageClass)
|
||||||
|
//old and new should have deference
|
||||||
|
storageClassCapability.Spec.Features.Volume.Create = !storageClassCapability.Spec.Features.Volume.Create
|
||||||
|
|
||||||
|
fixture := newFixture(t, true)
|
||||||
|
// Object exist
|
||||||
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
||||||
fixture.csiDriverLister = append(fixture.csiDriverLister, csiDriver)
|
|
||||||
fixture.snapshotClassObjects = append(fixture.snapshotClassObjects, snapshotClass)
|
fixture.snapshotClassObjects = append(fixture.snapshotClassObjects, snapshotClass)
|
||||||
fixture.snapshotClassLister = append(fixture.snapshotClassLister, snapshotClass)
|
fixture.snapshotClassLister = append(fixture.snapshotClassLister, snapshotClass)
|
||||||
fixture.capabilityObjects = append(fixture.capabilityObjects, storageClassCapability)
|
fixture.capabilityObjects = append(fixture.capabilityObjects, storageClassCapability)
|
||||||
fixture.storageClassCapabilityLister = append(fixture.storageClassCapabilityLister, storageClassCapability)
|
fixture.storageClassCapabilityLister = append(fixture.storageClassCapabilityLister, storageClassCapability)
|
||||||
|
|
||||||
// Action expected
|
fixture.expectUpdateStorageClassAction(storageClassUpdate)
|
||||||
fixture.expectUpdateStorageClassCapabilitiesAction(storageClassCapabilityUpdate)
|
|
||||||
|
|
||||||
// Run test
|
// Run test
|
||||||
fixture.run(getKey(storageClass, t))
|
fixture.run(getKey(storageClass, t))
|
||||||
@@ -374,12 +404,9 @@ func TestDeleteStorageClass(t *testing.T) {
|
|||||||
snapshotClass := newSnapshotClass(storageClass)
|
snapshotClass := newSnapshotClass(storageClass)
|
||||||
storageClassCapability := newStorageClassCapability(storageClass)
|
storageClassCapability := newStorageClassCapability(storageClass)
|
||||||
|
|
||||||
csiDriver := newCSIDriver(storageClass)
|
fixture := newFixture(t, true)
|
||||||
|
|
||||||
fixture := newFixture(t, true, true)
|
|
||||||
// Object exist
|
// Object exist
|
||||||
fixture.storageObjects = append(fixture.storageObjects, csiDriver)
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
fixture.csiDriverLister = append(fixture.csiDriverLister, csiDriver)
|
|
||||||
fixture.snapshotClassObjects = append(fixture.snapshotClassObjects, snapshotClass)
|
fixture.snapshotClassObjects = append(fixture.snapshotClassObjects, snapshotClass)
|
||||||
fixture.snapshotClassLister = append(fixture.snapshotClassLister, snapshotClass)
|
fixture.snapshotClassLister = append(fixture.snapshotClassLister, snapshotClass)
|
||||||
fixture.capabilityObjects = append(fixture.capabilityObjects, storageClassCapability)
|
fixture.capabilityObjects = append(fixture.capabilityObjects, storageClassCapability)
|
||||||
@@ -394,7 +421,8 @@ func TestDeleteStorageClass(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateStorageClassNotSupportSnapshot(t *testing.T) {
|
func TestCreateStorageClassNotSupportSnapshot(t *testing.T) {
|
||||||
fixture := newFixture(t, false, true)
|
// K8S version < 1.17.0
|
||||||
|
fixture := newFixture(t, false)
|
||||||
storageClass := newStorageClass("csi-example", "csi.example.com")
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
storageClassUpdate := storageClass.DeepCopy()
|
storageClassUpdate := storageClass.DeepCopy()
|
||||||
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "false"}
|
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "false"}
|
||||||
@@ -402,32 +430,31 @@ func TestCreateStorageClassNotSupportSnapshot(t *testing.T) {
|
|||||||
storageClassCapability.Spec.Features.Snapshot.Create = false
|
storageClassCapability.Spec.Features.Snapshot.Create = false
|
||||||
storageClassCapability.Spec.Features.Snapshot.List = false
|
storageClassCapability.Spec.Features.Snapshot.List = false
|
||||||
provisionerCapability := newProvisionerCapability(storageClass)
|
provisionerCapability := newProvisionerCapability(storageClass)
|
||||||
csiDriver := newCSIDriver(storageClass)
|
|
||||||
|
// Objects exist
|
||||||
// Objects exist
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
fixture.storageObjects = append(fixture.storageObjects, storageClass, csiDriver)
|
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
||||||
fixture.storageClassLister = append(fixture.storageClassLister, storageClass)
|
fixture.capabilityObjects = append(fixture.capabilityObjects, provisionerCapability)
|
||||||
fixture.csiDriverLister = append(fixture.csiDriverLister, csiDriver)
|
fixture.provisionerCapabilityLister = append(fixture.provisionerCapabilityLister, provisionerCapability)
|
||||||
fixture.capabilityObjects = append(fixture.capabilityObjects, provisionerCapability)
|
|
||||||
fixture.provisionerCapabilityLister = append(fixture.provisionerCapabilityLister, provisionerCapability)
|
// Action expected
|
||||||
|
fixture.expectUpdateStorageClassAction(storageClassUpdate)
|
||||||
// Action expected
|
fixture.expectCreateStorageClassCapabilitiesAction(storageClassCapability)
|
||||||
fixture.expectUpdateStorageClassAction(storageClassUpdate)
|
|
||||||
fixture.expectCreateStorageClassCapabilitiesAction(storageClassCapability)
|
// Run test
|
||||||
|
fixture.run(getKey(storageClass, t))
|
||||||
// Run test
|
}
|
||||||
fixture.run(getKey(storageClass, t))
|
|
||||||
}
|
func TestCreateStorageClassNotHaveSnapshotCap(t *testing.T) {
|
||||||
|
// Storage has no snapshot capability
|
||||||
func TestCreateStorageClassInTree(t *testing.T) {
|
fixture := newFixture(t, true)
|
||||||
// InTree Storage has no snapshot capability
|
storageClass := newStorageClass("csi-example", "csi.example.com")
|
||||||
fixture := newFixture(t, true, true)
|
storageClassUpdate := storageClass.DeepCopy()
|
||||||
storageClass := newStorageClass("csi-example", "csi.example.com")
|
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "false"}
|
||||||
storageClassUpdate := storageClass.DeepCopy()
|
storageClassCapability := newStorageClassCapability(storageClass)
|
||||||
storageClassUpdate.Annotations = map[string]string{annotationSupportSnapshot: "false"}
|
storageClassCapability.Spec.Features.Snapshot.Create = false
|
||||||
storageClassCapability := newStorageClassCapability(storageClass)
|
provisionerCapability := newProvisionerCapability(storageClass)
|
||||||
storageClassCapability.Spec.Features.Snapshot.Create = false
|
provisionerCapability.Spec.Features.Snapshot.Create = false
|
||||||
provisionerCapability := newProvisionerCapability(storageClass)
|
|
||||||
|
|
||||||
// Objects exist
|
// Objects exist
|
||||||
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
fixture.storageObjects = append(fixture.storageObjects, storageClass)
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package capability
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
"google.golang.org/grpc/keepalive"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apis/storage/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dialDuration = time.Second * 5
|
|
||||||
requestDuration = time.Second * 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func csiCapability(csiAddress string) (*v1alpha1.StorageClassCapabilitySpec, error) {
|
|
||||||
csiConn, err := connect(csiAddress)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() { _ = csiConn.Close() }()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), requestDuration)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
spec := &v1alpha1.StorageClassCapabilitySpec{}
|
|
||||||
err = addPluginCapabilities(ctx, csiConn, spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = addControllerCapabilities(ctx, csiConn, spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = addNodeCapabilities(ctx, csiConn, spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return spec, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPluginCapabilities(ctx context.Context, conn *grpc.ClientConn, spec *v1alpha1.StorageClassCapabilitySpec) error {
|
|
||||||
identityClient := csi.NewIdentityClient(conn)
|
|
||||||
pluginCapabilitiesResponse, err := identityClient.GetPluginCapabilities(ctx, &csi.GetPluginCapabilitiesRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, capability := range pluginCapabilitiesResponse.GetCapabilities() {
|
|
||||||
if capability == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if capability.GetService().GetType() == csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS {
|
|
||||||
spec.Features.Topology = true
|
|
||||||
}
|
|
||||||
volumeExpansion := capability.GetVolumeExpansion()
|
|
||||||
if volumeExpansion != nil {
|
|
||||||
switch volumeExpansion.GetType() {
|
|
||||||
case csi.PluginCapability_VolumeExpansion_ONLINE:
|
|
||||||
spec.Features.Volume.Expand = v1alpha1.ExpandModeOnline
|
|
||||||
case csi.PluginCapability_VolumeExpansion_OFFLINE:
|
|
||||||
spec.Features.Volume.Expand = v1alpha1.ExpandModeOffline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addControllerCapabilities(ctx context.Context, conn *grpc.ClientConn, spec *v1alpha1.StorageClassCapabilitySpec) error {
|
|
||||||
controllerClient := csi.NewControllerClient(conn)
|
|
||||||
controllerCapabilitiesResponse, err := controllerClient.ControllerGetCapabilities(ctx, &csi.ControllerGetCapabilitiesRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, capability := range controllerCapabilitiesResponse.GetCapabilities() {
|
|
||||||
switch capability.GetRpc().GetType() {
|
|
||||||
case csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME:
|
|
||||||
spec.Features.Volume.Create = true
|
|
||||||
case csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME:
|
|
||||||
spec.Features.Volume.Attach = true
|
|
||||||
case csi.ControllerServiceCapability_RPC_LIST_VOLUMES:
|
|
||||||
spec.Features.Volume.List = true
|
|
||||||
case csi.ControllerServiceCapability_RPC_CLONE_VOLUME:
|
|
||||||
spec.Features.Volume.Clone = true
|
|
||||||
case csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT:
|
|
||||||
spec.Features.Snapshot.Create = true
|
|
||||||
case csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS:
|
|
||||||
spec.Features.Snapshot.List = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addNodeCapabilities(ctx context.Context, conn *grpc.ClientConn, spec *v1alpha1.StorageClassCapabilitySpec) error {
|
|
||||||
nodeClient := csi.NewNodeClient(conn)
|
|
||||||
controllerCapabilitiesResponse, err := nodeClient.NodeGetCapabilities(ctx, &csi.NodeGetCapabilitiesRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, capability := range controllerCapabilitiesResponse.GetCapabilities() {
|
|
||||||
switch capability.GetRpc().GetType() {
|
|
||||||
case csi.NodeServiceCapability_RPC_GET_VOLUME_STATS:
|
|
||||||
spec.Features.Volume.Stats = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect address by GRPC
|
|
||||||
func connect(address string) (*grpc.ClientConn, error) {
|
|
||||||
dialOptions := []grpc.DialOption{
|
|
||||||
grpc.WithInsecure(),
|
|
||||||
}
|
|
||||||
u, err := url.Parse(address)
|
|
||||||
if err == nil && (!u.IsAbs() || u.Scheme == "unix") {
|
|
||||||
dialOptions = append(dialOptions,
|
|
||||||
grpc.WithDialer(
|
|
||||||
func(addr string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
return net.DialTimeout("unix", u.Path, timeout)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
// This is necessary when connecting via TCP and does not hurt
|
|
||||||
// when using Unix domain sockets. It ensures that gRPC detects a dead connection
|
|
||||||
// in a timely manner.
|
|
||||||
dialOptions = append(dialOptions,
|
|
||||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{PermitWithoutStream: true}))
|
|
||||||
|
|
||||||
conn, err := grpc.Dial(address, dialOptions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), dialDuration)
|
|
||||||
defer cancel()
|
|
||||||
for {
|
|
||||||
if !conn.WaitForStateChange(ctx, conn.GetState()) {
|
|
||||||
return conn, errors.New("connection timed out")
|
|
||||||
}
|
|
||||||
if conn.GetState() == connectivity.Ready {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2020 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package capability
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"k8s.io/apimachinery/pkg/util/rand"
|
|
||||||
"k8s.io/klog"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apis/storage/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DefaultControllerRPCType = []csi.ControllerServiceCapability_RPC_Type{
|
|
||||||
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
|
||||||
csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
|
|
||||||
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
|
|
||||||
csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultNodeRPCType = []csi.NodeServiceCapability_RPC_Type{
|
|
||||||
csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
|
|
||||||
csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
|
|
||||||
csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultPluginCapability = []*csi.PluginCapability{
|
|
||||||
{
|
|
||||||
Type: &csi.PluginCapability_Service_{
|
|
||||||
Service: &csi.PluginCapability_Service{
|
|
||||||
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: &csi.PluginCapability_VolumeExpansion_{
|
|
||||||
VolumeExpansion: &csi.PluginCapability_VolumeExpansion{
|
|
||||||
Type: csi.PluginCapability_VolumeExpansion_OFFLINE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeCSIServer struct {
|
|
||||||
csi.UnimplementedIdentityServer
|
|
||||||
csi.UnimplementedControllerServer
|
|
||||||
csi.UnimplementedNodeServer
|
|
||||||
network string
|
|
||||||
address string
|
|
||||||
server *grpc.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestCSIServer(port int) (csiServer *fakeCSIServer, address string) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
address = fmt.Sprintf("localhost:%d", +port)
|
|
||||||
csiServer = newFakeCSIServer("tcp", address)
|
|
||||||
} else {
|
|
||||||
address = filepath.Join(os.TempDir(), "csi.sock"+rand.String(4))
|
|
||||||
csiServer = newFakeCSIServer("unix", address)
|
|
||||||
address = "unix://" + address
|
|
||||||
}
|
|
||||||
return csiServer, address
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeCSIServer(network, address string) *fakeCSIServer {
|
|
||||||
return &fakeCSIServer{
|
|
||||||
network: network,
|
|
||||||
address: address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeCSIServer) run() {
|
|
||||||
listener, err := net.Listen(f.network, f.address)
|
|
||||||
if err != nil {
|
|
||||||
klog.Error("fake CSI server listen failed, ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server := grpc.NewServer()
|
|
||||||
csi.RegisterIdentityServer(server, f)
|
|
||||||
csi.RegisterControllerServer(server, f)
|
|
||||||
csi.RegisterNodeServer(server, f)
|
|
||||||
go func() {
|
|
||||||
err = server.Serve(listener)
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "stopped") {
|
|
||||||
klog.Error("fake CSI server serve failed, ", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
f.server = server
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeCSIServer) stop() {
|
|
||||||
if f.server != nil {
|
|
||||||
f.server.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*fakeCSIServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
|
|
||||||
return &csi.GetPluginCapabilitiesResponse{Capabilities: DefaultPluginCapability}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*fakeCSIServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
|
|
||||||
var capabilities []*csi.ControllerServiceCapability
|
|
||||||
for _, rpcType := range DefaultControllerRPCType {
|
|
||||||
capability := &csi.ControllerServiceCapability{
|
|
||||||
Type: &csi.ControllerServiceCapability_Rpc{
|
|
||||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
|
||||||
Type: rpcType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
capabilities = append(capabilities, capability)
|
|
||||||
}
|
|
||||||
return &csi.ControllerGetCapabilitiesResponse{Capabilities: capabilities}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*fakeCSIServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) {
|
|
||||||
var capabilities []*csi.NodeServiceCapability
|
|
||||||
for _, rpcType := range DefaultNodeRPCType {
|
|
||||||
capability := &csi.NodeServiceCapability{
|
|
||||||
Type: &csi.NodeServiceCapability_Rpc{
|
|
||||||
Rpc: &csi.NodeServiceCapability_RPC{
|
|
||||||
Type: rpcType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
capabilities = append(capabilities, capability)
|
|
||||||
}
|
|
||||||
return &csi.NodeGetCapabilitiesResponse{Capabilities: capabilities}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_CSICapability(t *testing.T) {
|
|
||||||
fakeCSIServer, address := newTestCSIServer(30087)
|
|
||||||
fakeCSIServer.run()
|
|
||||||
defer fakeCSIServer.stop()
|
|
||||||
|
|
||||||
specGot, err := csiCapability(address)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
specExpected := newStorageClassCapabilitySpec()
|
|
||||||
if diff := cmp.Diff(specGot, specExpected); diff != "" {
|
|
||||||
t.Errorf("%T differ (-got, +want): %s", specExpected, diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStorageClassCapabilitySpec() *v1alpha1.StorageClassCapabilitySpec {
|
|
||||||
return &v1alpha1.StorageClassCapabilitySpec{
|
|
||||||
Features: v1alpha1.CapabilityFeatures{
|
|
||||||
Topology: false,
|
|
||||||
Volume: v1alpha1.VolumeFeature{
|
|
||||||
Create: true,
|
|
||||||
Attach: false,
|
|
||||||
List: false,
|
|
||||||
Clone: true,
|
|
||||||
Stats: true,
|
|
||||||
Expand: v1alpha1.ExpandModeOffline,
|
|
||||||
},
|
|
||||||
Snapshot: v1alpha1.SnapshotFeature{
|
|
||||||
Create: true,
|
|
||||||
List: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user