Skip to content

Commit 9fad047

Browse files
authored
[cinder-csi-plugin] support multi-node attachments (kubernetes#1073)
1 parent 5a6ca97 commit 9fad047

File tree

4 files changed

+76
-40
lines changed

4 files changed

+76
-40
lines changed

docs/using-cinder-csi-plugin.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,25 @@ Following prerequisites needed for volume cloning to work :
436436

437437
Sample yamls can be found [here](https://github.com/kubernetes/cloud-provider-openstack/tree/master/examples/cinder-csi-plugin/clone)
438438

439+
### Multi-Attach Volumes
440+
441+
To avail the multiattach feature of cinder, specify the ID/name of cinder volume type that includes an extra-spec capability setting of `multiattach=<is> True` in storage class parameters as shown below.
442+
443+
> Note: This volume type must exist in cinder already (`openstack volume type list`)
444+
445+
This should enable to attach a volume to multiple hosts/servers simultaneously.
446+
447+
Example:
448+
449+
```
450+
apiVersion: storage.k8s.io/v1
451+
kind: StorageClass
452+
metadata:
453+
name: csi-sc-cinderplugin
454+
provisioner: cinder.csi.openstack.org
455+
parameters:
456+
type: <multiattach-volume-type>
457+
439458
## Running Sanity Tests
440459
441460
Sanity tests create a real instance of driver and fake cloud provider.

pkg/csi/cinder/controllerserver.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,18 @@ type controllerServer struct {
4141
}
4242

4343
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
44+
klog.V(4).Infof("CreateVolume: called with args %+v", *req)
4445

4546
// Volume Name
4647
volName := req.GetName()
48+
volCapabilities := req.GetVolumeCapabilities()
4749

4850
if len(volName) == 0 {
49-
return nil, status.Error(codes.InvalidArgument, "CreateVolume request missing Volume Name")
51+
return nil, status.Error(codes.InvalidArgument, "[CreateVolume] missing Volume Name")
5052
}
5153

52-
if req.VolumeCapabilities == nil {
53-
return nil, status.Error(codes.InvalidArgument, "CreateVolume request missing Volume capability")
54+
if volCapabilities == nil {
55+
return nil, status.Error(codes.InvalidArgument, "[CreateVolume] missing Volume capability")
5456
}
5557

5658
// Volume Size - Default is 1 GiB
@@ -123,18 +125,20 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
123125
}
124126

125127
vol, err := cloud.CreateVolume(volName, volSizeGB, volType, volAvailability, snapshotID, sourcevolID, &properties)
128+
126129
if err != nil {
127-
klog.V(3).Infof("Failed to CreateVolume: %v", err)
130+
klog.Errorf("Failed to CreateVolume: %v", err)
128131
return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume failed with error %v", err))
129132

130133
}
131134

132-
klog.V(4).Infof("Create volume %s in Availability Zone: %s of size %d GiB", vol.ID, vol.AvailabilityZone, vol.Size)
135+
klog.V(4).Infof("CreateVolume: Successfully created volume %s in Availability Zone: %s of size %d GiB", vol.ID, vol.AvailabilityZone, vol.Size)
133136

134137
return getCreateVolumeResponse(vol), nil
135138
}
136139

137140
func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
141+
klog.V(4).Infof("DeleteVolume: called with args %+v", *req)
138142

139143
// Volume Delete
140144
volID := req.GetVolumeId()
@@ -147,11 +151,11 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
147151
klog.V(3).Infof("Volume %s is already deleted.", volID)
148152
return &csi.DeleteVolumeResponse{}, nil
149153
}
150-
klog.V(3).Infof("Failed to DeleteVolume: %v", err)
154+
klog.Errorf("Failed to DeleteVolume: %v", err)
151155
return nil, status.Error(codes.Internal, fmt.Sprintf("DeleteVolume failed with error %v", err))
152156
}
153157

154-
klog.V(4).Infof("Delete volume %s", volID)
158+
klog.V(4).Infof("DeleteVolume: Successfully deleted volume %s", volID)
155159

156160
return &csi.DeleteVolumeResponse{}, nil
157161
}

pkg/csi/cinder/controllerserver_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ func TestCreateVolume(t *testing.T) {
5757
Name: FakeVolName,
5858
VolumeCapabilities: []*csi.VolumeCapability{
5959
{
60-
AccessType: &csi.VolumeCapability_Mount{
61-
Mount: &csi.VolumeCapability_MountVolume{},
60+
AccessMode: &csi.VolumeCapability_AccessMode{
61+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
6262
},
6363
},
6464
},
65+
6566
AccessibilityRequirements: &csi.TopologyRequirement{
6667
Requisite: []*csi.Topology{
6768
{
@@ -109,8 +110,8 @@ func TestCreateVolumeFromSnapshot(t *testing.T) {
109110
Name: FakeVolName,
110111
VolumeCapabilities: []*csi.VolumeCapability{
111112
{
112-
AccessType: &csi.VolumeCapability_Mount{
113-
Mount: &csi.VolumeCapability_MountVolume{},
113+
AccessMode: &csi.VolumeCapability_AccessMode{
114+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
114115
},
115116
},
116117
},
@@ -157,8 +158,8 @@ func TestCreateVolumeFromSourceVolume(t *testing.T) {
157158
Name: FakeVolName,
158159
VolumeCapabilities: []*csi.VolumeCapability{
159160
{
160-
AccessType: &csi.VolumeCapability_Mount{
161-
Mount: &csi.VolumeCapability_MountVolume{},
161+
AccessMode: &csi.VolumeCapability_AccessMode{
162+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
162163
},
163164
},
164165
},
@@ -195,8 +196,8 @@ func TestCreateVolumeDuplicate(t *testing.T) {
195196
Name: "fake-duplicate",
196197
VolumeCapabilities: []*csi.VolumeCapability{
197198
{
198-
AccessType: &csi.VolumeCapability_Mount{
199-
Mount: &csi.VolumeCapability_MountVolume{},
199+
AccessMode: &csi.VolumeCapability_AccessMode{
200+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
200201
},
201202
},
202203
},

pkg/csi/cinder/openstack/openstack_volumes.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func (os *OpenStack) CheckBlockStorageAPI() error {
5959

6060
// CreateVolume creates a volume of given size
6161
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, snapshotID string, sourcevolID string, tags *map[string]string) (*volumes.Volume, error) {
62+
6263
opts := &volumes.CreateOpts{
6364
Name: name,
6465
Size: size,
@@ -145,20 +146,31 @@ func (os *OpenStack) GetVolume(volumeID string) (*volumes.Volume, error) {
145146

146147
// AttachVolume attaches given cinder volume to the compute
147148
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
149+
computeServiceClient := os.compute
150+
148151
volume, err := os.GetVolume(volumeID)
149152
if err != nil {
150153
return "", err
151154
}
152155

153-
if len(volume.Attachments) > 0 {
154-
if instanceID == volume.Attachments[0].ServerID {
156+
for _, att := range volume.Attachments {
157+
if instanceID == att.ServerID {
155158
klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
156159
return volume.ID, nil
157160
}
158-
return "", fmt.Errorf("disk %s is attached to a different instance (%s)", volumeID, volume.Attachments[0].ServerID)
159161
}
160162

161-
_, err = volumeattach.Create(os.compute, instanceID, &volumeattach.CreateOpts{
163+
if volume.Multiattach {
164+
// For multiattach volumes, supported compute api version is 2.60
165+
// Init a local thread safe copy of the compute ServiceClient
166+
computeServiceClient, err = openstack.NewComputeV2(os.compute.ProviderClient, os.epOpts)
167+
if err != nil {
168+
return "", err
169+
}
170+
computeServiceClient.Microversion = "2.60"
171+
}
172+
173+
_, err = volumeattach.Create(computeServiceClient, instanceID, &volumeattach.CreateOpts{
162174
VolumeID: volume.ID,
163175
}).Extract()
164176

@@ -209,21 +221,19 @@ func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
209221
return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
210222
}
211223

212-
if len(volume.Attachments) > 0 {
213-
if volume.Attachments[0].ServerID != instanceID {
214-
return fmt.Errorf("disk: %s is not attached to compute: %s", volume.Name, instanceID)
224+
// Incase volume is of type multiattach, it could be attached to more than one instance
225+
for _, att := range volume.Attachments {
226+
if att.ServerID == instanceID {
227+
err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
228+
if err != nil {
229+
return fmt.Errorf("failed to detach volume %s from compute %s : %v", volume.ID, instanceID, err)
230+
}
231+
klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)
232+
return nil
215233
}
216-
err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
217-
if err != nil {
218-
return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err)
219-
}
220-
klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)
221-
222-
} else {
223-
return fmt.Errorf("disk: %s has no attachments", volume.Name)
224234
}
225235

226-
return nil
236+
return fmt.Errorf("disk: %s has no attachments or not attached to compute %s", volume.ID, instanceID)
227237
}
228238

229239
// WaitDiskDetached waits for detached
@@ -259,13 +269,15 @@ func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string,
259269
return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
260270
}
261271

262-
if len(volume.Attachments) > 0 && volume.Attachments[0].ServerID != "" {
263-
if instanceID == volume.Attachments[0].ServerID {
264-
return volume.Attachments[0].Device, nil
272+
if len(volume.Attachments) > 0 {
273+
for _, att := range volume.Attachments {
274+
if att.ServerID == instanceID {
275+
return att.Device, nil
276+
}
265277
}
266-
return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.Attachments[0].ServerID)
278+
return "", fmt.Errorf("disk %q is not attached to compute: %q", volumeID, instanceID)
267279
}
268-
return "", fmt.Errorf("volume %s has no ServerId", volumeID)
280+
return "", fmt.Errorf("volume %s has no Attachments", volumeID)
269281
}
270282

271283
// ExpandVolume expands the volume to new size
@@ -315,11 +327,11 @@ func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
315327
if err != nil {
316328
return false, err
317329
}
318-
319-
if len(volume.Attachments) > 0 {
320-
return instanceID == volume.Attachments[0].ServerID, nil
330+
for _, att := range volume.Attachments {
331+
if att.ServerID == instanceID {
332+
return true, nil
333+
}
321334
}
322-
323335
return false, nil
324336
}
325337

0 commit comments

Comments
 (0)