Skip to content

Commit 70dd10c

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#41785 from jamiehannaford/cinder-performance
Automatic merge from submit-queue (batch tested with PRs 38505, 41785, 46315) Only retrieve relevant volumes **What this PR does / why we need it**: Improves performance for Cinder volume attach/detach calls. Currently when Cinder volumes are attached or detached, functions try to retrieve details about the volume from the Nova API. Because some only have the volume name not its UUID, they use the list function in gophercloud to iterate over all volumes to find a match. This incurs severe performance problems on OpenStack projects with lots of volumes (sometimes thousands) since it needs to send a new request when the current page does not contain a match. A better way of doing this is use the `?name=XXX` query parameter to refine the results. **Which issue this PR fixes**: kubernetes#26404 **Special notes for your reviewer**: There were 2 ways of addressing this problem: 1. Use the `name` query parameter 2. Instead of using the list function, switch to using volume UUIDs and use the GET function instead. You'd need to change the signature of a few functions though, such as [`DeleteVolume`](https://github.com/kubernetes/kubernetes/blob/master/pkg/volume/cinder/cinder.go#L49), so I'm not sure how backwards compatible that is. Since #1 does effectively the same as #2, I went with it because it ensures BC. One assumption that is made is that the `volumeName` being retrieved matches exactly the name of the volume in Cinder. I'm not sure how accurate that is, but I see no reason why cloud providers would want to append/prefix things arbitrarily. **Release note**: ```release-note Improves performance of Cinder volume attach/detach operations ```
2 parents 2bc097b + 4bd71a3 commit 70dd10c

File tree

6 files changed

+243
-309
lines changed

6 files changed

+243
-309
lines changed

pkg/cloudprovider/providers/openstack/openstack_volumes.go

Lines changed: 73 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ import (
3131
volumes_v1 "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
3232
volumes_v2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
3333
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
34-
"github.com/gophercloud/gophercloud/pagination"
3534
"github.com/prometheus/client_golang/prometheus"
3635

3736
"github.com/golang/glog"
3837
)
3938

4039
type volumeService interface {
4140
createVolume(opts VolumeCreateOpts) (string, string, error)
42-
getVolume(diskName string) (Volume, error)
41+
getVolume(volumeID string) (Volume, error)
4342
deleteVolume(volumeName string) error
4443
}
4544

@@ -123,107 +122,73 @@ func (volumes *VolumesV2) createVolume(opts VolumeCreateOpts) (string, string, e
123122
return vol.ID, vol.AvailabilityZone, nil
124123
}
125124

126-
func (volumes *VolumesV1) getVolume(diskName string) (Volume, error) {
127-
var volume_v1 volumes_v1.Volume
128-
var volume Volume
125+
func (volumes *VolumesV1) getVolume(volumeID string) (Volume, error) {
129126
startTime := time.Now()
130-
err := volumes_v1.List(volumes.blockstorage, nil).EachPage(func(page pagination.Page) (bool, error) {
131-
vols, err := volumes_v1.ExtractVolumes(page)
132-
if err != nil {
133-
glog.Errorf("Failed to extract volumes: %v", err)
134-
return false, err
135-
} else {
136-
for _, v := range vols {
137-
glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
138-
if v.Name == diskName || strings.Contains(v.ID, diskName) {
139-
volume_v1 = v
140-
return true, nil
141-
}
142-
}
143-
}
144-
// if it reached here then no disk with the given name was found.
145-
errmsg := fmt.Sprintf("Unable to find disk: %s", diskName)
146-
return false, errors.New(errmsg)
147-
})
127+
volumeV1, err := volumes_v1.Get(volumes.blockstorage, volumeID).Extract()
148128
timeTaken := time.Since(startTime).Seconds()
149129
recordOpenstackOperationMetric("get_v1_volume", timeTaken, err)
150130
if err != nil {
151-
glog.Errorf("Error occurred getting volume: %s", diskName)
152-
return volume, err
131+
glog.Errorf("Error occurred getting volume by ID: %s", volumeID)
132+
return Volume{}, err
153133
}
154134

155-
volume.ID = volume_v1.ID
156-
volume.Name = volume_v1.Name
157-
volume.Status = volume_v1.Status
135+
volume := Volume{
136+
ID: volumeV1.ID,
137+
Name: volumeV1.Name,
138+
Status: volumeV1.Status,
139+
}
158140

159-
if len(volume_v1.Attachments) > 0 && volume_v1.Attachments[0]["server_id"] != nil {
160-
volume.AttachedServerId = volume_v1.Attachments[0]["server_id"].(string)
161-
volume.AttachedDevice = volume_v1.Attachments[0]["device"].(string)
141+
if len(volumeV1.Attachments) > 0 && volumeV1.Attachments[0]["server_id"] != nil {
142+
volume.AttachedServerId = volumeV1.Attachments[0]["server_id"].(string)
143+
volume.AttachedDevice = volumeV1.Attachments[0]["device"].(string)
162144
}
163145

164146
return volume, nil
165147
}
166148

167-
func (volumes *VolumesV2) getVolume(diskName string) (Volume, error) {
168-
var volume_v2 volumes_v2.Volume
169-
var volume Volume
149+
func (volumes *VolumesV2) getVolume(volumeID string) (Volume, error) {
170150
startTime := time.Now()
171-
err := volumes_v2.List(volumes.blockstorage, nil).EachPage(func(page pagination.Page) (bool, error) {
172-
vols, err := volumes_v2.ExtractVolumes(page)
173-
if err != nil {
174-
glog.Errorf("Failed to extract volumes: %v", err)
175-
return false, err
176-
} else {
177-
for _, v := range vols {
178-
glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
179-
if v.Name == diskName || strings.Contains(v.ID, diskName) {
180-
volume_v2 = v
181-
return true, nil
182-
}
183-
}
184-
}
185-
// if it reached here then no disk with the given name was found.
186-
errmsg := fmt.Sprintf("Unable to find disk: %s", diskName)
187-
return false, errors.New(errmsg)
188-
})
151+
volumeV2, err := volumes_v2.Get(volumes.blockstorage, volumeID).Extract()
189152
timeTaken := time.Since(startTime).Seconds()
190153
recordOpenstackOperationMetric("get_v2_volume", timeTaken, err)
191154
if err != nil {
192-
glog.Errorf("Error occurred getting volume: %s", diskName)
193-
return volume, err
155+
glog.Errorf("Error occurred getting volume by ID: %s", volumeID)
156+
return Volume{}, err
194157
}
195158

196-
volume.ID = volume_v2.ID
197-
volume.Name = volume_v2.Name
198-
volume.Status = volume_v2.Status
159+
volume := Volume{
160+
ID: volumeV2.ID,
161+
Name: volumeV2.Name,
162+
Status: volumeV2.Status,
163+
}
199164

200-
if len(volume_v2.Attachments) > 0 {
201-
volume.AttachedServerId = volume_v2.Attachments[0].ServerID
202-
volume.AttachedDevice = volume_v2.Attachments[0].Device
165+
if len(volumeV2.Attachments) > 0 {
166+
volume.AttachedServerId = volumeV2.Attachments[0].ServerID
167+
volume.AttachedDevice = volumeV2.Attachments[0].Device
203168
}
204169

205170
return volume, nil
206171
}
207172

208-
func (volumes *VolumesV1) deleteVolume(volumeName string) error {
173+
func (volumes *VolumesV1) deleteVolume(volumeID string) error {
209174
startTime := time.Now()
210-
err := volumes_v1.Delete(volumes.blockstorage, volumeName).ExtractErr()
175+
err := volumes_v1.Delete(volumes.blockstorage, volumeID).ExtractErr()
211176
timeTaken := time.Since(startTime).Seconds()
212177
recordOpenstackOperationMetric("delete_v1_volume", timeTaken, err)
213178
if err != nil {
214-
glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
179+
glog.Errorf("Cannot delete volume %s: %v", volumeID, err)
215180
}
216181

217182
return err
218183
}
219184

220-
func (volumes *VolumesV2) deleteVolume(volumeName string) error {
185+
func (volumes *VolumesV2) deleteVolume(volumeID string) error {
221186
startTime := time.Now()
222-
err := volumes_v2.Delete(volumes.blockstorage, volumeName).ExtractErr()
187+
err := volumes_v2.Delete(volumes.blockstorage, volumeID).ExtractErr()
223188
timeTaken := time.Since(startTime).Seconds()
224189
recordOpenstackOperationMetric("delete_v2_volume", timeTaken, err)
225190
if err != nil {
226-
glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
191+
glog.Errorf("Cannot delete volume %s: %v", volumeID, err)
227192
}
228193

229194
return err
@@ -246,8 +211,8 @@ func (os *OpenStack) OperationPending(diskName string) (bool, string, error) {
246211
}
247212

248213
// Attaches given cinder volume to the compute running kubelet
249-
func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) {
250-
volume, err := os.getVolume(diskName)
214+
func (os *OpenStack) AttachDisk(instanceID, volumeID string) (string, error) {
215+
volume, err := os.getVolume(volumeID)
251216
if err != nil {
252217
return "", err
253218
}
@@ -266,11 +231,11 @@ func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, err
266231

267232
if volume.AttachedServerId != "" {
268233
if instanceID == volume.AttachedServerId {
269-
glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID)
234+
glog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
270235
return volume.ID, nil
271236
}
272-
glog.V(2).Infof("Disk %q is attached to a different compute (%q), detaching", diskName, volume.AttachedServerId)
273-
err = os.DetachDisk(volume.AttachedServerId, diskName)
237+
glog.V(2).Infof("Disk %s is attached to a different instance (%s), detaching", volumeID, volume.AttachedServerId)
238+
err = os.DetachDisk(volume.AttachedServerId, volumeID)
274239
if err != nil {
275240
return "", err
276241
}
@@ -284,16 +249,16 @@ func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, err
284249
timeTaken := time.Since(startTime).Seconds()
285250
recordOpenstackOperationMetric("attach_disk", timeTaken, err)
286251
if err != nil {
287-
glog.Errorf("Failed to attach %s volume to %s compute: %v", diskName, instanceID, err)
252+
glog.Errorf("Failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
288253
return "", err
289254
}
290-
glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID)
255+
glog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID)
291256
return volume.ID, nil
292257
}
293258

294-
// Detaches given cinder volume from the compute running kubelet
295-
func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error {
296-
volume, err := os.getVolume(partialDiskId)
259+
// DetachDisk detaches given cinder volume from the compute running kubelet
260+
func (os *OpenStack) DetachDisk(instanceID, volumeID string) error {
261+
volume, err := os.getVolume(volumeID)
297262
if err != nil {
298263
return err
299264
}
@@ -330,26 +295,24 @@ func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error {
330295
return nil
331296
}
332297

333-
// Takes a partial/full disk id or diskname
334-
func (os *OpenStack) getVolume(diskName string) (Volume, error) {
335-
298+
// Retrieves Volume by its ID.
299+
func (os *OpenStack) getVolume(volumeID string) (Volume, error) {
336300
volumes, err := os.volumeService("")
337301
if err != nil || volumes == nil {
338302
glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
339303
return Volume{}, err
340304
}
341-
342-
return volumes.getVolume(diskName)
305+
return volumes.getVolume(volumeID)
343306
}
344307

345308
// Create a volume of given size (in GiB)
346309
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, error) {
347-
348310
volumes, err := os.volumeService("")
349311
if err != nil || volumes == nil {
350312
glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
351313
return "", "", err
352314
}
315+
353316
opts := VolumeCreateOpts{
354317
Name: name,
355318
Size: size,
@@ -359,27 +322,28 @@ func (os *OpenStack) CreateVolume(name string, size int, vtype, availability str
359322
if tags != nil {
360323
opts.Metadata = *tags
361324
}
362-
volumeId, volumeAZ, err := volumes.createVolume(opts)
325+
326+
volumeID, volumeAZ, err := volumes.createVolume(opts)
363327

364328
if err != nil {
365329
glog.Errorf("Failed to create a %d GB volume: %v", size, err)
366330
return "", "", err
367331
}
368332

369-
glog.Infof("Created volume %v in Availability Zone: %v", volumeId, volumeAZ)
370-
return volumeId, volumeAZ, nil
333+
glog.Infof("Created volume %v in Availability Zone: %v", volumeID, volumeAZ)
334+
return volumeID, volumeAZ, nil
371335
}
372336

373337
// GetDevicePath returns the path of an attached block storage volume, specified by its id.
374-
func (os *OpenStack) GetDevicePath(diskId string) string {
338+
func (os *OpenStack) GetDevicePath(volumeID string) string {
375339
// Build a list of candidate device paths
376340
candidateDeviceNodes := []string{
377341
// KVM
378-
fmt.Sprintf("virtio-%s", diskId[:20]),
342+
fmt.Sprintf("virtio-%s", volumeID[:20]),
379343
// KVM virtio-scsi
380-
fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", diskId[:20]),
344+
fmt.Sprintf("scsi-0QEMU_QEMU_HARDDISK_%s", volumeID[:20]),
381345
// ESXi
382-
fmt.Sprintf("wwn-0x%s", strings.Replace(diskId, "-", "", -1)),
346+
fmt.Sprintf("wwn-0x%s", strings.Replace(volumeID, "-", "", -1)),
383347
}
384348

385349
files, _ := ioutil.ReadDir("/dev/disk/by-id/")
@@ -393,17 +357,17 @@ func (os *OpenStack) GetDevicePath(diskId string) string {
393357
}
394358
}
395359

396-
glog.Warningf("Failed to find device for the diskid: %q\n", diskId)
360+
glog.Warningf("Failed to find device for the volumeID: %q\n", volumeID)
397361
return ""
398362
}
399363

400-
func (os *OpenStack) DeleteVolume(volumeName string) error {
401-
used, err := os.diskIsUsed(volumeName)
364+
func (os *OpenStack) DeleteVolume(volumeID string) error {
365+
used, err := os.diskIsUsed(volumeID)
402366
if err != nil {
403367
return err
404368
}
405369
if used {
406-
msg := fmt.Sprintf("Cannot delete the volume %q, it's still attached to a node", volumeName)
370+
msg := fmt.Sprintf("Cannot delete the volume %q, it's still attached to a node", volumeID)
407371
return k8s_volume.NewDeletedVolumeInUseError(msg)
408372
}
409373

@@ -413,19 +377,19 @@ func (os *OpenStack) DeleteVolume(volumeName string) error {
413377
return err
414378
}
415379

416-
err = volumes.deleteVolume(volumeName)
380+
err = volumes.deleteVolume(volumeID)
417381
if err != nil {
418-
glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
382+
glog.Errorf("Cannot delete volume %s: %v", volumeID, err)
419383
}
420384
return nil
421385

422386
}
423387

424388
// Get device path of attached volume to the compute running kubelet, as known by cinder
425-
func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
389+
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
426390
// See issue #33128 - Cinder does not always tell you the right device path, as such
427391
// we must only use this value as a last resort.
428-
volume, err := os.getVolume(diskName)
392+
volume, err := os.getVolume(volumeID)
429393
if err != nil {
430394
return "", err
431395
}
@@ -440,47 +404,41 @@ func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (
440404
// see http://developer.openstack.org/api-ref-blockstorage-v1.html
441405
return volume.AttachedDevice, nil
442406
} else {
443-
errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, volume.AttachedServerId)
407+
errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerId)
444408
glog.Errorf(errMsg)
445409
return "", errors.New(errMsg)
446410
}
447411
}
448-
return "", fmt.Errorf("volume %s has not ServerId.", diskName)
412+
return "", fmt.Errorf("volume %s has no ServerId.", volumeID)
449413
}
450414

451415
// query if a volume is attached to a compute instance
452-
func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) {
453-
volume, err := os.getVolume(diskName)
416+
func (os *OpenStack) DiskIsAttached(instanceID, volumeID string) (bool, error) {
417+
volume, err := os.getVolume(volumeID)
454418
if err != nil {
455419
return false, err
456420
}
457421

458-
if instanceID == volume.AttachedServerId {
459-
return true, nil
460-
}
461-
return false, nil
422+
return instanceID == volume.AttachedServerId, nil
462423
}
463424

464425
// query if a list of volumes are attached to a compute instance
465-
func (os *OpenStack) DisksAreAttached(diskNames []string, instanceID string) (map[string]bool, error) {
426+
func (os *OpenStack) DisksAreAttached(instanceID string, volumeIDs []string) (map[string]bool, error) {
466427
attached := make(map[string]bool)
467-
for _, diskName := range diskNames {
468-
is_attached, _ := os.DiskIsAttached(diskName, instanceID)
469-
attached[diskName] = is_attached
428+
for _, volumeID := range volumeIDs {
429+
isAttached, _ := os.DiskIsAttached(instanceID, volumeID)
430+
attached[volumeID] = isAttached
470431
}
471432
return attached, nil
472433
}
473434

474435
// diskIsUsed returns true a disk is attached to any node.
475-
func (os *OpenStack) diskIsUsed(diskName string) (bool, error) {
476-
volume, err := os.getVolume(diskName)
436+
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
437+
volume, err := os.getVolume(volumeID)
477438
if err != nil {
478439
return false, err
479440
}
480-
if volume.AttachedServerId != "" {
481-
return true, nil
482-
}
483-
return false, nil
441+
return volume.AttachedServerId != "", nil
484442
}
485443

486444
// query if we should trust the cinder provide deviceName, See issue #33128

0 commit comments

Comments
 (0)