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:

committed by
Walter A. Boring IV (hemna)

parent
9cb7232655
commit
480afa893a
@@ -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':
|
||||||
|
@@ -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):
|
||||||
|
@@ -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):
|
||||||
|
@@ -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)
|
||||||
|
@@ -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.
|
||||||
|
Reference in New Issue
Block a user