Implement new attach Cinder flow

This change integrates support of the Cinder 3.44
volume attachment API.

The patch bumps the compute service version to
check whether all the compute nodes are upgraded to the
version that can handle attach and detach with the new
flow.

To enable the new flow we also need the 3.44 or higher
microversion from Cinder. We check that in the API and
if it's not available we fall back to the old
attach/detach flow.

Co-Authored-By: Ildiko Vancsa <ildiko.vancsa@gmail.com>

Partially Implements: blueprint cinder-new-attach-apis
Change-Id: Ifc01dbf98545104c998ab96f65ff8623a6db0f28
This commit is contained in:
John Griffith 2017-07-21 13:31:57 +02:00 committed by Matt Riedemann
parent 6c043b7ea6
commit d4ac674750

@ -32,6 +32,7 @@ from oslo_config import cfg
import oslo_messaging as messaging
from oslo_messaging import conffixture as messaging_conffixture
from oslo_privsep import daemon as privsep_daemon
from oslo_utils import uuidutils
from requests import adapters
from wsgi_intercept import interceptor
@ -1459,6 +1460,179 @@ class CinderFixture(fixtures.Fixture):
fake_unreserve_volume)
# TODO(mriedem): We can probably pull some of the common parts from the
# CinderFixture into a common mixin class for things like the variables
# and fake_get.
class CinderFixtureNewAttachFlow(fixtures.Fixture):
"""A fixture to volume operations with the new Cinder attach/detach API"""
# the default project_id in OSAPIFixtures
tenant_id = '6f70656e737461636b20342065766572'
SWAP_OLD_VOL = 'a07f71dc-8151-4e7d-a0cc-cd24a3f11113'
SWAP_NEW_VOL = '227cc671-f30b-4488-96fd-7d0bf13648d8'
SWAP_ERR_OLD_VOL = '828419fa-3efb-4533-b458-4267ca5fe9b1'
SWAP_ERR_NEW_VOL = '9c6d9c2d-7a8f-4c80-938d-3bf062b8d489'
SWAP_ERR_ATTACH_ID = '4a3cd440-b9c2-11e1-afa6-0800200c9a66'
# This represents a bootable image-backed volume to test
# boot-from-volume scenarios.
IMAGE_BACKED_VOL = '6ca404f3-d844-4169-bb96-bc792f37de98'
def __init__(self, test):
super(CinderFixtureNewAttachFlow, self).__init__()
self.test = test
self.swap_error = False
self.swap_volume_instance_uuid = None
self.swap_volume_instance_error_uuid = None
self.attachment_error_id = None
# This is a map of instance UUIDs mapped to a list of volume IDs.
# This map gets updated on attach/detach operations.
self.attachments = collections.defaultdict(list)
def setUp(self):
super(CinderFixtureNewAttachFlow, self).setUp()
def fake_get(self_api, context, volume_id):
# Check for the special swap volumes.
if volume_id in (CinderFixture.SWAP_OLD_VOL,
CinderFixture.SWAP_ERR_OLD_VOL):
volume = {
'status': 'available',
'display_name': 'TEST1',
'attach_status': 'detached',
'id': volume_id,
'size': 1
}
if ((self.swap_volume_instance_uuid and
volume_id == CinderFixture.SWAP_OLD_VOL) or
(self.swap_volume_instance_error_uuid and
volume_id == CinderFixture.SWAP_ERR_OLD_VOL)):
instance_uuid = (self.swap_volume_instance_uuid
if volume_id == CinderFixture.SWAP_OLD_VOL
else self.swap_volume_instance_error_uuid)
volume.update({
'status': 'in-use',
'attachments': {
instance_uuid: {
'mountpoint': '/dev/vdb',
'attachment_id': volume_id
}
},
'attach_status': 'attached'
})
return volume
# Check to see if the volume is attached.
for instance_uuid, volumes in self.attachments.items():
if volume_id in volumes:
# The volume is attached.
volume = {
'status': 'in-use',
'display_name': volume_id,
'attach_status': 'attached',
'id': volume_id,
'size': 1,
'attachments': {
instance_uuid: {
'attachment_id': volume_id,
'mountpoint': '/dev/vdb'
}
}
}
break
else:
# This is a test that does not care about the actual details.
volume = {
'status': 'available',
'display_name': 'TEST2',
'attach_status': 'detached',
'id': volume_id,
'size': 1
}
# update the status based on existing attachments
has_attachment = any(
[volume['id'] in attachments
for attachments in self.attachments.values()])
volume['status'] = 'attached' if has_attachment else 'detached'
# Check for our special image-backed volume.
if volume_id == self.IMAGE_BACKED_VOL:
# Make it a bootable volume.
volume['bootable'] = True
# Add the image_id metadata.
volume['volume_image_metadata'] = {
# There would normally be more image metadata in here...
'image_id': '155d900f-4e14-4e4c-a73d-069cbf4541e6'
}
return volume
def fake_migrate_volume_completion(self, context, old_volume_id,
new_volume_id, error):
return {'save_volume_id': new_volume_id}
def fake_attachment_create(_self, context, volume_id, instance_uuid,
connector=None):
attachment_id = uuidutils.generate_uuid()
if self.attachment_error_id is not None:
attachment_id = self.attachment_error_id
attachment = {'id': attachment_id, 'connection_info': {'data': {}}}
self.attachments['instance_uuid'].append(instance_uuid)
self.attachments[instance_uuid].append(volume_id)
return attachment
def fake_attachment_delete(_self, context, attachment_id):
instance_uuid = self.attachments['instance_uuid'][0]
del self.attachments[instance_uuid][0]
del self.attachments['instance_uuid'][0]
if attachment_id == CinderFixtureNewAttachFlow.SWAP_ERR_ATTACH_ID:
self.swap_error = True
def fake_attachment_update(_self, context, attachment_id, connector):
attachment_ref = {'driver_volume_type': 'fake_type',
'id': attachment_id,
'connection_info': {'data':
{'foo': 'bar',
'target_lun': '1'}}}
if attachment_id == CinderFixtureNewAttachFlow.SWAP_ERR_ATTACH_ID:
attachment_ref = {'connection_info': ()}
return attachment_ref
def fake_attachment_get(_self, context, attachment_id):
attachment_ref = {'driver_volume_type': 'fake_type',
'id': attachment_id,
'connection_info': {'data':
{'foo': 'bar',
'target_lun': '1'}}}
return attachment_ref
self.test.stub_out('nova.volume.cinder.API.attachment_create',
fake_attachment_create)
self.test.stub_out('nova.volume.cinder.API.attachment_delete',
fake_attachment_delete)
self.test.stub_out('nova.volume.cinder.API.attachment_update',
fake_attachment_update)
self.test.stub_out('nova.volume.cinder.API.attachment_complete',
lambda *args, **kwargs: None)
self.test.stub_out('nova.volume.cinder.API.attachment_get',
fake_attachment_get)
self.test.stub_out('nova.volume.cinder.API.begin_detaching',
lambda *args, **kwargs: None)
self.test.stub_out('nova.volume.cinder.API.get',
fake_get)
self.test.stub_out(
'nova.volume.cinder.API.migrate_volume_completion',
fake_migrate_volume_completion)
self.test.stub_out('nova.volume.cinder.API.roll_detaching',
lambda *args, **kwargs: None)
self.test.stub_out('nova.volume.cinder.is_microversion_supported',
lambda *args, **kwargs: None)
class PlacementApiClient(object):
def __init__(self, placement_fixture):
self.fixture = placement_fixture