Skip to content

Commit 572d05f

Browse files
committed
Add Block volume support for CSI provisioner
In CSI provisioner, below three logics need to be implemented, to add Block volume support to CSI provisioner: 1. Add SupportsBlock that properly returns whether Storage Provider's plugin supports block (this is checked by using ValidateVolumeCapabilities), 2. Pass BlockVolume instead of MountVolume to CreateVolume if volumeMode is set to be Block on Provision, 3. Set volumeMode to PV returned by Provision. Also, below 4 test cases for TestSupportsBlock and 2 test cases for TestProvision are added. TestSupportsBlock: 1. ValidateVolumeCapabilities return (true, nil) case: return true expected 2. ValidateVolumeCapabilities return (false, nil) case: return false expected 3. ValidateVolumeCapabilities return (true, err) case: return false expected 4. ValidateVolumeCapabilities return (false, err) case: return false expected TestProvision: 1. volumeMode=Filesystem PVC case: return Filesystem PV expected 2. volumeMode=Block PVC case: return Block PV expected Fixes kubernetes-csi#110
1 parent 7257060 commit 572d05f

File tree

2 files changed

+164
-11
lines changed

2 files changed

+164
-11
lines changed

pkg/controller/controller.go

+55-10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/golang/glog"
3232

3333
"github.com/kubernetes-incubator/external-storage/lib/controller"
34+
"github.com/kubernetes-incubator/external-storage/lib/util"
3435

3536
snapapi "github.com/kubernetes-csi/external-snapshotter/pkg/apis/volumesnapshot/v1alpha1"
3637
snapclientset "github.com/kubernetes-csi/external-snapshotter/pkg/client/clientset/versioned"
@@ -96,9 +97,20 @@ var (
9697
accessMode = &csi.VolumeCapability_AccessMode{
9798
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
9899
}
99-
accessType = &csi.VolumeCapability_Mount{
100+
accessTypeMount = &csi.VolumeCapability_Mount{
100101
Mount: &csi.VolumeCapability_MountVolume{},
101102
}
103+
accessTypeBlock = &csi.VolumeCapability_Block{
104+
Block: &csi.VolumeCapability_BlockVolume{},
105+
}
106+
volumeCapabilityMountSingle = &csi.VolumeCapability{
107+
AccessType: accessTypeMount,
108+
AccessMode: accessMode,
109+
}
110+
volumeCapabilityBlockSingle = &csi.VolumeCapability{
111+
AccessType: accessTypeBlock,
112+
AccessMode: accessMode,
113+
}
102114
// Each provisioner have a identify string to distinguish with others. This
103115
// identify string will be added in PV annoations under this key.
104116
provisionerIDKey = "storage.kubernetes.io/csiProvisionerIdentity"
@@ -370,17 +382,22 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
370382
capacity := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
371383
volSizeBytes := capacity.Value()
372384

385+
volumeCapabilities := []*csi.VolumeCapability{
386+
volumeCapabilityMountSingle,
387+
}
388+
389+
if util.CheckPersistentVolumeClaimModeBlock(options.PVC) {
390+
volumeCapabilities = []*csi.VolumeCapability{
391+
volumeCapabilityBlockSingle,
392+
}
393+
}
394+
373395
// Create a CSI CreateVolumeRequest and Response
374396
req := csi.CreateVolumeRequest{
375397

376-
Name: pvName,
377-
Parameters: options.Parameters,
378-
VolumeCapabilities: []*csi.VolumeCapability{
379-
{
380-
AccessType: accessType,
381-
AccessMode: accessMode,
382-
},
383-
},
398+
Name: pvName,
399+
Parameters: options.Parameters,
400+
VolumeCapabilities: volumeCapabilities,
384401
CapacityRange: &csi.CapacityRange{
385402
RequiredBytes: int64(volSizeBytes),
386403
},
@@ -497,7 +514,6 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
497514
CSI: &v1.CSIPersistentVolumeSource{
498515
Driver: driverName,
499516
VolumeHandle: p.volumeIdToHandle(rep.Volume.Id),
500-
FSType: fsType,
501517
VolumeAttributes: volumeAttributes,
502518
ControllerPublishSecretRef: controllerPublishSecretRef,
503519
NodeStageSecretRef: nodeStageSecretRef,
@@ -507,6 +523,16 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
507523
},
508524
}
509525

526+
// Set VolumeMode to PV if it is passed via PVC spec when Block feature is enabled
527+
if options.PVC.Spec.VolumeMode != nil {
528+
pv.Spec.VolumeMode = options.PVC.Spec.VolumeMode
529+
}
530+
531+
// Set FSType if PV is not Block Volume
532+
if !util.CheckPersistentVolumeClaimModeBlock(options.PVC) {
533+
pv.Spec.PersistentVolumeSource.CSI.FSType = fsType
534+
}
535+
510536
glog.Infof("successfully created PV %+v", pv.Spec.PersistentVolumeSource)
511537

512538
return pv, nil
@@ -604,6 +630,25 @@ func (p *csiProvisioner) Delete(volume *v1.PersistentVolume) error {
604630
return err
605631
}
606632

633+
func (p *csiProvisioner) SupportsBlock() bool {
634+
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
635+
defer cancel()
636+
637+
client := csi.NewControllerClient(p.grpcClient)
638+
req := csi.ValidateVolumeCapabilitiesRequest{
639+
VolumeCapabilities: []*csi.VolumeCapability{
640+
volumeCapabilityBlockSingle,
641+
},
642+
}
643+
644+
rsp, err := client.ValidateVolumeCapabilities(ctx, &req)
645+
if err != nil {
646+
return false
647+
}
648+
649+
return rsp.Supported
650+
}
651+
607652
//TODO use a unique volume handle from and to Id
608653
func (p *csiProvisioner) volumeIdToHandle(id string) string {
609654
return id

pkg/controller/controller_test.go

+109-1
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,13 @@ func createFakePVC(requestBytes int64) *v1.PersistentVolumeClaim {
675675
}
676676
}
677677

678+
// createFakePVCWithVolumeMode returns PVC with VolumeMode
679+
func createFakePVCWithVolumeMode(requestBytes int64, volumeMode v1.PersistentVolumeMode) *v1.PersistentVolumeClaim {
680+
claim := createFakePVC(requestBytes)
681+
claim.Spec.VolumeMode = &volumeMode
682+
return claim
683+
}
684+
678685
func TestGetSecretReference(t *testing.T) {
679686
testcases := map[string]struct {
680687
nameKey string
@@ -798,13 +805,67 @@ func TestGetSecretReference(t *testing.T) {
798805
}
799806
}
800807

808+
func TestSupportsBlock(t *testing.T) {
809+
var (
810+
volCapTrue = csi.ValidateVolumeCapabilitiesResponse{
811+
Supported: true,
812+
}
813+
volCapFalse = csi.ValidateVolumeCapabilitiesResponse{
814+
Supported: false,
815+
}
816+
generalError = fmt.Errorf("")
817+
)
818+
819+
testcases := []struct {
820+
name string
821+
volCapResp csi.ValidateVolumeCapabilitiesResponse
822+
volCapErr error
823+
expectSupportsBlock bool
824+
}{
825+
{"ValidateVolumeCapabilities returns (true, nil): expects true", volCapTrue, nil, true},
826+
{"ValidateVolumeCapabilities returns (false, nil): expects false", volCapFalse, nil, false},
827+
{"ValidateVolumeCapabilities returns (true, error): expects false", volCapTrue, generalError, false}, // This won't happen.
828+
{"ValidateVolumeCapabilities returns (false, error): expects false", volCapFalse, generalError, false},
829+
}
830+
831+
mockController, driver, _, controllerServer, csiConn, err := createMockServer(t)
832+
if err != nil {
833+
t.Fatal(err)
834+
}
835+
defer mockController.Finish()
836+
defer driver.Stop()
837+
838+
for _, tc := range testcases {
839+
var clientSet kubernetes.Interface
840+
841+
clientSet = fakeclientset.NewSimpleClientset()
842+
csiProvisioner := NewCSIProvisioner(clientSet, driver.Address(), 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil)
843+
844+
controllerServer.EXPECT().ValidateVolumeCapabilities(gomock.Any(), gomock.Any()).Return(&tc.volCapResp, tc.volCapErr).Times(1)
845+
846+
if csiBlockProvisioner, ok := csiProvisioner.(controller.BlockProvisioner); ok {
847+
supportsBlock := csiBlockProvisioner.SupportsBlock()
848+
if tc.expectSupportsBlock != supportsBlock {
849+
t.Errorf("test %q: Expected %v got %v", tc.name, tc.expectSupportsBlock, supportsBlock)
850+
}
851+
} else {
852+
t.Errorf("test %q: Expected csiProvisioner implements BlockProvisioner interface got %v", tc.name, ok)
853+
}
854+
}
855+
}
856+
801857
func TestProvision(t *testing.T) {
802-
var requestedBytes int64 = 100
858+
var (
859+
requestedBytes int64 = 100
860+
volumeModeFileSystem = v1.PersistentVolumeFilesystem
861+
volumeModeBlock = v1.PersistentVolumeBlock
862+
)
803863

804864
type pvSpec struct {
805865
Name string
806866
ReclaimPolicy v1.PersistentVolumeReclaimPolicy
807867
AccessModes []v1.PersistentVolumeAccessMode
868+
VolumeMode *v1.PersistentVolumeMode
808869
Capacity v1.ResourceList
809870
CSIPVS *v1.CSIPersistentVolumeSource
810871
}
@@ -917,6 +978,49 @@ func TestProvision(t *testing.T) {
917978
},
918979
},
919980
},
981+
"provision with volume mode(Filesystem)": {
982+
volOpts: controller.VolumeOptions{
983+
PVName: "test-name",
984+
PVC: createFakePVCWithVolumeMode(requestedBytes, volumeModeFileSystem),
985+
Parameters: map[string]string{},
986+
},
987+
expectedPVSpec: &pvSpec{
988+
Name: "test-testi",
989+
Capacity: v1.ResourceList{
990+
v1.ResourceName(v1.ResourceStorage): bytesToGiQuantity(requestedBytes),
991+
},
992+
VolumeMode: &volumeModeFileSystem,
993+
CSIPVS: &v1.CSIPersistentVolumeSource{
994+
Driver: "test-driver",
995+
VolumeHandle: "test-volume-id",
996+
FSType: "ext4",
997+
VolumeAttributes: map[string]string{
998+
"storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner",
999+
},
1000+
},
1001+
},
1002+
},
1003+
"provision with volume mode(Block)": {
1004+
volOpts: controller.VolumeOptions{
1005+
PVName: "test-name",
1006+
PVC: createFakePVCWithVolumeMode(requestedBytes, volumeModeBlock),
1007+
Parameters: map[string]string{},
1008+
},
1009+
expectedPVSpec: &pvSpec{
1010+
Name: "test-testi",
1011+
Capacity: v1.ResourceList{
1012+
v1.ResourceName(v1.ResourceStorage): bytesToGiQuantity(requestedBytes),
1013+
},
1014+
VolumeMode: &volumeModeBlock,
1015+
CSIPVS: &v1.CSIPersistentVolumeSource{
1016+
Driver: "test-driver",
1017+
VolumeHandle: "test-volume-id",
1018+
VolumeAttributes: map[string]string{
1019+
"storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner",
1020+
},
1021+
},
1022+
},
1023+
},
9201024
"fail to get secret reference": {
9211025
volOpts: controller.VolumeOptions{
9221026
PVName: "test-name",
@@ -1065,6 +1169,10 @@ func TestProvision(t *testing.T) {
10651169
t.Errorf("test %q: expected access modes: %v, got: %v", k, tc.expectedPVSpec.AccessModes, pv.Spec.AccessModes)
10661170
}
10671171

1172+
if !reflect.DeepEqual(pv.Spec.VolumeMode, tc.expectedPVSpec.VolumeMode) {
1173+
t.Errorf("test %q: expected volumeMode: %v, got: %v", k, tc.expectedPVSpec.VolumeMode, pv.Spec.VolumeMode)
1174+
}
1175+
10681176
if !reflect.DeepEqual(pv.Spec.Capacity, tc.expectedPVSpec.Capacity) {
10691177
t.Errorf("test %q: expected capacity: %v, got: %v", k, tc.expectedPVSpec.Capacity, pv.Spec.Capacity)
10701178
}

0 commit comments

Comments
 (0)