Skip to content

Commit 6750548

Browse files
Add support for multinode attach (#175)
* multinode attach * Unit tests added
1 parent 47c4d96 commit 6750548

File tree

2 files changed

+264
-4
lines changed

2 files changed

+264
-4
lines changed

pkg/driver/controller.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,28 @@ import (
2929
)
3030

3131
var (
32-
// TODO: explore multi-node attach
32+
33+
// Supported volume capabilities
3334
volumeCaps = []csi.VolumeCapability_AccessMode{
3435
{
3536
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
3637
},
38+
{
39+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
40+
},
41+
{
42+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
43+
},
44+
}
45+
46+
// Shareable volume capabilities
47+
shareableVolumeCaps = []csi.VolumeCapability_AccessMode{
48+
{
49+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
50+
},
51+
{
52+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
53+
},
3754
}
3855

3956
// controllerCaps represents the capability of controller service
@@ -101,7 +118,7 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
101118
if !isValidVolumeCapabilities(volCaps) {
102119
modes := util.GetAccessModes(volCaps)
103120
stringModes := strings.Join(*modes, ", ")
104-
errString := "Volume capabilities " + stringModes + " not supported. Only AccessModes[ReadWriteOnce] supported."
121+
errString := "Volume capabilities " + stringModes + " not supported. Only AccessModes [ReadWriteOnce], [ReadWriteMany], [ReadOnlyMany] supported."
105122
return nil, status.Error(codes.InvalidArgument, errString)
106123
}
107124

@@ -116,8 +133,9 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol
116133
}
117134
}
118135

136+
shareable := isShareableVolume(volCaps)
119137
opts := &cloud.DiskOptions{
120-
Shareable: false,
138+
Shareable: shareable,
121139
CapacityBytes: volSizeBytes,
122140
VolumeType: volumeType,
123141
}
@@ -197,7 +215,7 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs
197215
if !isValidVolumeCapabilities(caps) {
198216
modes := util.GetAccessModes(caps)
199217
stringModes := strings.Join(*modes, ", ")
200-
errString := "Volume capabilities " + stringModes + " not supported. Only AccessModes[ReadWriteOnce] supported."
218+
errString := "Volume capabilities " + stringModes + " not supported. Only AccessModes [ReadWriteOnce], [ReadWriteMany], [ReadOnlyMany] supported."
201219
return nil, status.Error(codes.InvalidArgument, errString)
202220
}
203221

@@ -383,6 +401,25 @@ func isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) bool {
383401
return foundAll
384402
}
385403

404+
// Check if the volume is shareable
405+
func isShareableVolume(volCaps []*csi.VolumeCapability) bool {
406+
isShareable := func(cap *csi.VolumeCapability) bool {
407+
for _, c := range shareableVolumeCaps {
408+
if c.GetMode() == cap.AccessMode.GetMode() {
409+
return true
410+
}
411+
}
412+
return false
413+
}
414+
415+
for _, c := range volCaps {
416+
if isShareable(c) {
417+
return true
418+
}
419+
}
420+
return false
421+
}
422+
386423
func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
387424
klog.V(4).Infof("CreateSnapshot: called with args %+v", req)
388425
return nil, status.Error(codes.Unimplemented, "")

pkg/driver/controller_test.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,169 @@ func TestCreateVolume(t *testing.T) {
9393
},
9494
},
9595

96+
{
97+
name: "csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER",
98+
testFunc: func(t *testing.T) {
99+
req := &csi.CreateVolumeRequest{
100+
Name: "random-vol-name",
101+
CapacityRange: stdCapRange,
102+
VolumeCapabilities: []*csi.VolumeCapability{
103+
{
104+
AccessType: &csi.VolumeCapability_Mount{
105+
Mount: &csi.VolumeCapability_MountVolume{},
106+
},
107+
AccessMode: &csi.VolumeCapability_AccessMode{
108+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
109+
},
110+
},
111+
},
112+
Parameters: nil,
113+
}
114+
115+
ctx := context.Background()
116+
mockDisk := &cloud.Disk{
117+
VolumeID: req.Name,
118+
CapacityGiB: util.BytesToGiB(stdVolSize),
119+
Shareable: true,
120+
}
121+
mockDiskOpts := &cloud.DiskOptions{
122+
Shareable: true,
123+
CapacityBytes: stdVolSize,
124+
}
125+
126+
mockCtl := gomock.NewController(t)
127+
defer mockCtl.Finish()
128+
129+
mockCloud := mocks.NewMockCloud(mockCtl)
130+
mockCloud.EXPECT().GetDiskByName(gomock.Eq(req.Name)).Return(nil, nil)
131+
mockCloud.EXPECT().CreateDisk(gomock.Eq(req.Name), mockDiskOpts).Return(mockDisk, nil)
132+
133+
powervsDriver := controllerService{
134+
cloud: mockCloud,
135+
driverOptions: &Options{},
136+
volumeLocks: util.NewVolumeLocks(),
137+
}
138+
139+
_, err := powervsDriver.CreateVolume(ctx, req)
140+
if err != nil {
141+
srvErr, ok := status.FromError(err)
142+
if !ok {
143+
t.Fatalf("Could not get error status code from error: %v", srvErr)
144+
}
145+
t.Fatalf("Unexpected error: %v", srvErr.Code())
146+
}
147+
148+
},
149+
},
150+
{
151+
name: "csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY",
152+
testFunc: func(t *testing.T) {
153+
req := &csi.CreateVolumeRequest{
154+
Name: "random-vol-name",
155+
CapacityRange: stdCapRange,
156+
VolumeCapabilities: []*csi.VolumeCapability{
157+
{
158+
AccessType: &csi.VolumeCapability_Mount{
159+
Mount: &csi.VolumeCapability_MountVolume{},
160+
},
161+
AccessMode: &csi.VolumeCapability_AccessMode{
162+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
163+
},
164+
},
165+
},
166+
Parameters: nil,
167+
}
168+
169+
ctx := context.Background()
170+
mockDisk := &cloud.Disk{
171+
VolumeID: req.Name,
172+
CapacityGiB: util.BytesToGiB(stdVolSize),
173+
Shareable: true,
174+
}
175+
mockDiskOpts := &cloud.DiskOptions{
176+
Shareable: true,
177+
CapacityBytes: stdVolSize,
178+
}
179+
180+
mockCtl := gomock.NewController(t)
181+
defer mockCtl.Finish()
182+
183+
mockCloud := mocks.NewMockCloud(mockCtl)
184+
mockCloud.EXPECT().GetDiskByName(gomock.Eq(req.Name)).Return(nil, nil)
185+
mockCloud.EXPECT().CreateDisk(gomock.Eq(req.Name), mockDiskOpts).Return(mockDisk, nil)
186+
187+
powervsDriver := controllerService{
188+
cloud: mockCloud,
189+
driverOptions: &Options{},
190+
volumeLocks: util.NewVolumeLocks(),
191+
}
192+
193+
_, err := powervsDriver.CreateVolume(ctx, req)
194+
if err != nil {
195+
srvErr, ok := status.FromError(err)
196+
if !ok {
197+
t.Fatalf("Could not get error status code from error: %v", srvErr)
198+
}
199+
t.Fatalf("Unexpected error: %v", srvErr.Code())
200+
}
201+
202+
},
203+
},
204+
{
205+
name: "csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER",
206+
testFunc: func(t *testing.T) {
207+
req := &csi.CreateVolumeRequest{
208+
Name: "random-vol-name",
209+
CapacityRange: stdCapRange,
210+
VolumeCapabilities: []*csi.VolumeCapability{
211+
{
212+
AccessType: &csi.VolumeCapability_Mount{
213+
Mount: &csi.VolumeCapability_MountVolume{},
214+
},
215+
AccessMode: &csi.VolumeCapability_AccessMode{
216+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
217+
},
218+
},
219+
},
220+
Parameters: nil,
221+
}
222+
223+
ctx := context.Background()
224+
mockDisk := &cloud.Disk{
225+
VolumeID: req.Name,
226+
CapacityGiB: util.BytesToGiB(stdVolSize),
227+
Shareable: false,
228+
}
229+
mockDiskOpts := &cloud.DiskOptions{
230+
Shareable: false,
231+
CapacityBytes: stdVolSize,
232+
}
233+
234+
mockCtl := gomock.NewController(t)
235+
defer mockCtl.Finish()
236+
237+
mockCloud := mocks.NewMockCloud(mockCtl)
238+
mockCloud.EXPECT().GetDiskByName(gomock.Eq(req.Name)).Return(nil, nil)
239+
mockCloud.EXPECT().CreateDisk(gomock.Eq(req.Name), mockDiskOpts).Return(mockDisk, nil)
240+
241+
powervsDriver := controllerService{
242+
cloud: mockCloud,
243+
driverOptions: &Options{},
244+
volumeLocks: util.NewVolumeLocks(),
245+
}
246+
247+
_, err := powervsDriver.CreateVolume(ctx, req)
248+
if err != nil {
249+
srvErr, ok := status.FromError(err)
250+
if !ok {
251+
t.Fatalf("Could not get error status code from error: %v", srvErr)
252+
}
253+
t.Fatalf("Unexpected error: %v", srvErr.Code())
254+
}
255+
256+
},
257+
},
258+
96259
{
97260
name: "fail no name",
98261
testFunc: func(t *testing.T) {
@@ -1220,6 +1383,66 @@ func TestControllerExpandVolume(t *testing.T) {
12201383
}
12211384
}
12221385

1386+
func TestIsShareableVolume(t *testing.T) {
1387+
testCases := []struct {
1388+
name string
1389+
expectedShareable bool
1390+
testVolCap []*csi.VolumeCapability
1391+
}{
1392+
{
1393+
name: "Test csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER",
1394+
expectedShareable: false,
1395+
testVolCap: []*csi.VolumeCapability{
1396+
{
1397+
AccessType: &csi.VolumeCapability_Mount{
1398+
Mount: &csi.VolumeCapability_MountVolume{},
1399+
},
1400+
AccessMode: &csi.VolumeCapability_AccessMode{
1401+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
1402+
},
1403+
},
1404+
},
1405+
},
1406+
{
1407+
name: "Test csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER",
1408+
expectedShareable: true,
1409+
testVolCap: []*csi.VolumeCapability{
1410+
{
1411+
AccessType: &csi.VolumeCapability_Mount{
1412+
Mount: &csi.VolumeCapability_MountVolume{},
1413+
},
1414+
AccessMode: &csi.VolumeCapability_AccessMode{
1415+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
1416+
},
1417+
},
1418+
},
1419+
},
1420+
{
1421+
name: "Test csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY",
1422+
expectedShareable: true,
1423+
testVolCap: []*csi.VolumeCapability{
1424+
{
1425+
AccessType: &csi.VolumeCapability_Mount{
1426+
Mount: &csi.VolumeCapability_MountVolume{},
1427+
},
1428+
AccessMode: &csi.VolumeCapability_AccessMode{
1429+
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
1430+
},
1431+
},
1432+
},
1433+
},
1434+
}
1435+
1436+
for _, tc := range testCases {
1437+
t.Run(tc.name, func(t *testing.T) {
1438+
if res := isShareableVolume(tc.testVolCap); res != tc.expectedShareable {
1439+
t.Fatalf("Volume capability: %s expected value for shareable : %t got: %t", tc.testVolCap[0].AccessMode.GetMode().String(), tc.expectedShareable, res)
1440+
}
1441+
})
1442+
}
1443+
1444+
}
1445+
12231446
func checkExpectedErrorCode(t *testing.T, err error, expectedCode codes.Code) {
12241447
if err == nil {
12251448
t.Fatalf("Expected operation to fail but got no error")

0 commit comments

Comments
 (0)