Add volume multi attach support

This patch adds the new multiattach flag during volume
creation time. This patch adds the attachment_uuid
during detach time so cinder knows which
attachment it should detach.

This patch is needed by Cinder's multi-attach-volume
https://review.openstack.org/#/c/85847/

Change-Id: I98c6150cf548e9d1d8557770dbd3f88ec17a9b41
Implements: blueprint multi-attach-volume
This commit is contained in:
Walter A. Boring IV
2014-04-07 14:55:57 -07:00
committed by Walter A. Boring IV (hemna)
parent 9cb7232655
commit 480afa893a
5 changed files with 34 additions and 14 deletions

View File

@@ -40,6 +40,7 @@ def _stub_volume(**kwargs):
"snapshot_id": None, "snapshot_id": None,
"status": "available", "status": "available",
"volume_type": "None", "volume_type": "None",
"multiattach": "false",
"links": [ "links": [
{ {
"href": "http://localhost/v2/fake/volumes/1234", "href": "http://localhost/v2/fake/volumes/1234",
@@ -415,7 +416,7 @@ class FakeHTTPClient(base_client.HTTPClient):
assert (keys == ['instance_uuid', 'mode', 'mountpoint'] or assert (keys == ['instance_uuid', 'mode', 'mountpoint'] or
keys == ['host_name', 'mode', 'mountpoint']) keys == ['host_name', 'mode', 'mountpoint'])
elif action == 'os-detach': elif action == 'os-detach':
assert body[action] is None assert list(body[action]) == ['attachment_id']
elif action == 'os-reserve': elif action == 'os-reserve':
assert body[action] is None assert body[action] is None
elif action == 'os-unreserve': elif action == 'os-unreserve':

View File

@@ -125,7 +125,8 @@ class ShellTest(utils.TestCase):
'snapshot_id': None, 'snapshot_id': None,
'metadata': {'key1': '"--test1"'}, 'metadata': {'key1': '"--test1"'},
'volume_type': None, 'volume_type': None,
'description': None}} 'description': None,
'multiattach': False}}
self.assert_called_anytime('POST', '/volumes', expected) self.assert_called_anytime('POST', '/volumes', expected)
def test_metadata_args_limiter_display_name(self): def test_metadata_args_limiter_display_name(self):
@@ -145,7 +146,8 @@ class ShellTest(utils.TestCase):
'snapshot_id': None, 'snapshot_id': None,
'metadata': {'key1': '"--t1"'}, 'metadata': {'key1': '"--t1"'},
'volume_type': None, 'volume_type': None,
'description': None}} 'description': None,
'multiattach': False}}
self.assert_called_anytime('POST', '/volumes', expected) self.assert_called_anytime('POST', '/volumes', expected)
def test_delimit_metadata_args(self): def test_delimit_metadata_args(self):
@@ -165,7 +167,8 @@ class ShellTest(utils.TestCase):
'metadata': {'key1': '"test1"', 'metadata': {'key1': '"test1"',
'key2': '"test2"'}, 'key2': '"test2"'},
'volume_type': None, 'volume_type': None,
'description': None}} 'description': None,
'multiattach': False}}
self.assert_called_anytime('POST', '/volumes', expected) self.assert_called_anytime('POST', '/volumes', expected)
def test_delimit_metadata_args_display_name(self): def test_delimit_metadata_args_display_name(self):
@@ -185,7 +188,8 @@ class ShellTest(utils.TestCase):
'snapshot_id': None, 'snapshot_id': None,
'metadata': {'key1': '"t1"'}, 'metadata': {'key1': '"t1"'},
'volume_type': None, 'volume_type': None,
'description': None}} 'description': None,
'multiattach': False}}
self.assert_called_anytime('POST', '/volumes', expected) self.assert_called_anytime('POST', '/volumes', expected)
def test_list_filter_status(self): def test_list_filter_status(self):

View File

@@ -95,7 +95,8 @@ class VolumesTest(utils.TestCase):
'project_id': None, 'project_id': None,
'metadata': {}, 'metadata': {},
'source_replica': None, 'source_replica': None,
'consistencygroup_id': None}, 'consistencygroup_id': None,
'multiattach': False},
'OS-SCH-HNT:scheduler_hints': 'uuid'} 'OS-SCH-HNT:scheduler_hints': 'uuid'}
cs.assert_called('POST', '/volumes', body=expected) cs.assert_called('POST', '/volumes', body=expected)
@@ -111,7 +112,7 @@ class VolumesTest(utils.TestCase):
def test_detach(self): def test_detach(self):
v = cs.volumes.get('1234') v = cs.volumes.get('1234')
cs.volumes.detach(v) cs.volumes.detach(v, 'abc123')
cs.assert_called('POST', '/volumes/1234/action') cs.assert_called('POST', '/volumes/1234/action')
def test_reserve(self): def test_reserve(self):

View File

@@ -234,10 +234,12 @@ def do_list(cs, args):
if all_tenants: if all_tenants:
key_list = ['ID', 'Tenant ID', 'Status', 'Name', key_list = ['ID', 'Tenant ID', 'Status', 'Name',
'Size', 'Volume Type', 'Bootable', 'Attached to'] 'Size', 'Volume Type', 'Bootable', 'Multiattach',
'Attached to']
else: else:
key_list = ['ID', 'Status', 'Name', key_list = ['ID', 'Status', 'Name',
'Size', 'Volume Type', 'Bootable', 'Attached to'] 'Size', 'Volume Type', 'Bootable',
'Multiattach', 'Attached to']
if args.sort_key or args.sort_dir or args.sort: if args.sort_key or args.sort_dir or args.sort:
sortby_index = None sortby_index = None
else: else:
@@ -348,6 +350,12 @@ class CheckSizeArgForCreate(argparse.Action):
action='append', action='append',
default=[], default=[],
help='Scheduler hint, like in nova.') help='Scheduler hint, like in nova.')
@utils.arg('--allow-multiattach',
dest='multiattach',
action="store_true",
help=('Allow volume to be attached more than once.'
' Default=False'),
default=False)
@utils.service_type('volumev2') @utils.service_type('volumev2')
def do_create(cs, args): def do_create(cs, args):
"""Creates a volume.""" """Creates a volume."""
@@ -391,7 +399,8 @@ def do_create(cs, args):
imageRef=image_ref, imageRef=image_ref,
metadata=volume_metadata, metadata=volume_metadata,
scheduler_hints=hints, scheduler_hints=hints,
source_replica=args.source_replica) source_replica=args.source_replica,
multiattach=args.multiattach)
info = dict() info = dict()
volume = cs.volumes.get(volume.id) volume = cs.volumes.get(volume.id)

View File

@@ -179,8 +179,8 @@ class VolumeManager(base.ManagerWithFind):
volume_type=None, user_id=None, volume_type=None, user_id=None,
project_id=None, availability_zone=None, project_id=None, availability_zone=None,
metadata=None, imageRef=None, scheduler_hints=None, metadata=None, imageRef=None, scheduler_hints=None,
source_replica=None): source_replica=None, multiattach=False):
"""Creates a volume. """Create a volume.
:param size: Size of volume in GB :param size: Size of volume in GB
:param consistencygroup_id: ID of the consistencygroup :param consistencygroup_id: ID of the consistencygroup
@@ -197,6 +197,8 @@ class VolumeManager(base.ManagerWithFind):
:param source_replica: ID of source volume to clone replica :param source_replica: ID of source volume to clone replica
:param scheduler_hints: (optional extension) arbitrary key-value pairs :param scheduler_hints: (optional extension) arbitrary key-value pairs
specified by the client to help boot an instance specified by the client to help boot an instance
:param multiattach: Allow the volume to be attached to more than
one instance
:rtype: :class:`Volume` :rtype: :class:`Volume`
""" """
if metadata is None: if metadata is None:
@@ -219,6 +221,7 @@ class VolumeManager(base.ManagerWithFind):
'imageRef': imageRef, 'imageRef': imageRef,
'source_volid': source_volid, 'source_volid': source_volid,
'source_replica': source_replica, 'source_replica': source_replica,
'multiattach': multiattach,
}} }}
if scheduler_hints: if scheduler_hints:
@@ -393,13 +396,15 @@ class VolumeManager(base.ManagerWithFind):
body.update({'host_name': host_name}) body.update({'host_name': host_name})
return self._action('os-attach', volume, body) return self._action('os-attach', volume, body)
def detach(self, volume): def detach(self, volume, attachment_uuid=None):
"""Clear attachment metadata. """Clear attachment metadata.
:param volume: The :class:`Volume` (or its ID) :param volume: The :class:`Volume` (or its ID)
you would like to detach. you would like to detach.
:param attachment_uuid: The uuid of the volume attachment.
""" """
return self._action('os-detach', volume) return self._action('os-detach', volume,
{'attachment_id': attachment_uuid})
def reserve(self, volume): def reserve(self, volume):
"""Reserve this volume. """Reserve this volume.