Merge "Add client support in Cinder for volume replication"
This commit is contained in:
@@ -372,6 +372,10 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
assert list(body[action]) == ['bootable']
|
assert list(body[action]) == ['bootable']
|
||||||
elif action == 'os-unmanage':
|
elif action == 'os-unmanage':
|
||||||
assert body[action] is None
|
assert body[action] is None
|
||||||
|
elif action == 'os-promote-replica':
|
||||||
|
assert body[action] is None
|
||||||
|
elif action == 'os-reenable-replica':
|
||||||
|
assert body[action] is None
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Unexpected action: %s" % action)
|
raise AssertionError("Unexpected action: %s" % action)
|
||||||
return (resp, {}, _body)
|
return (resp, {}, _body)
|
||||||
@@ -835,3 +839,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
volume = _stub_volume(id='1234')
|
volume = _stub_volume(id='1234')
|
||||||
volume.update(kw['body']['volume'])
|
volume.update(kw['body']['volume'])
|
||||||
return (202, {}, {'volume': volume})
|
return (202, {}, {'volume': volume})
|
||||||
|
|
||||||
|
def post_os_promote_replica_1234(self, **kw):
|
||||||
|
return (202, {}, {})
|
||||||
|
|
||||||
|
def post_os_reenable_replica_1234(self, **kw):
|
||||||
|
return (202, {}, {})
|
||||||
|
@@ -137,6 +137,14 @@ class ShellTest(utils.TestCase):
|
|||||||
self.assert_called_anytime('POST', '/volumes', partial_body=expected)
|
self.assert_called_anytime('POST', '/volumes', partial_body=expected)
|
||||||
self.assert_called('GET', '/volumes/1234')
|
self.assert_called('GET', '/volumes/1234')
|
||||||
|
|
||||||
|
def test_create_volume_from_replica(self):
|
||||||
|
expected = {'volume': {'size': None}}
|
||||||
|
|
||||||
|
expected['volume']['source_replica'] = '1234'
|
||||||
|
self.run_command('create --source-replica=1234')
|
||||||
|
self.assert_called_anytime('POST', '/volumes', partial_body=expected)
|
||||||
|
self.assert_called('GET', '/volumes/1234')
|
||||||
|
|
||||||
def test_create_size_required_if_not_snapshot_or_clone(self):
|
def test_create_size_required_if_not_snapshot_or_clone(self):
|
||||||
self.assertRaises(SystemExit, self.run_command, 'create')
|
self.assertRaises(SystemExit, self.run_command, 'create')
|
||||||
|
|
||||||
@@ -522,3 +530,13 @@ class ShellTest(utils.TestCase):
|
|||||||
self.run_command('unmanage 1234')
|
self.run_command('unmanage 1234')
|
||||||
self.assert_called('POST', '/volumes/1234/action',
|
self.assert_called('POST', '/volumes/1234/action',
|
||||||
body={'os-unmanage': None})
|
body={'os-unmanage': None})
|
||||||
|
|
||||||
|
def test_replication_promote(self):
|
||||||
|
self.run_command('replication-promote 1234')
|
||||||
|
self.assert_called('POST', '/volumes/1234/action',
|
||||||
|
body={'os-promote-replica': None})
|
||||||
|
|
||||||
|
def test_replication_reenable(self):
|
||||||
|
self.run_command('replication-reenable 1234')
|
||||||
|
self.assert_called('POST', '/volumes/1234/action',
|
||||||
|
body={'os-reenable-replica': None})
|
||||||
|
@@ -66,7 +66,8 @@ class VolumesTest(utils.TestCase):
|
|||||||
'attach_status': 'detached',
|
'attach_status': 'detached',
|
||||||
'volume_type': None,
|
'volume_type': None,
|
||||||
'project_id': None,
|
'project_id': None,
|
||||||
'metadata': {}},
|
'metadata': {},
|
||||||
|
'source_replica': None},
|
||||||
'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)
|
||||||
|
|
||||||
|
@@ -226,7 +226,8 @@ def do_show(cs, args):
|
|||||||
|
|
||||||
class CheckSizeArgForCreate(argparse.Action):
|
class CheckSizeArgForCreate(argparse.Action):
|
||||||
def __call__(self, parser, args, values, option_string=None):
|
def __call__(self, parser, args, values, option_string=None):
|
||||||
if (values or args.snapshot_id or args.source_volid) is None:
|
if (values or args.snapshot_id or args.source_volid
|
||||||
|
or args.source_replica) is None:
|
||||||
parser.error('Size is a required parameter if snapshot '
|
parser.error('Size is a required parameter if snapshot '
|
||||||
'or source volume is not specified.')
|
'or source volume is not specified.')
|
||||||
setattr(args, self.dest, values)
|
setattr(args, self.dest, values)
|
||||||
@@ -251,6 +252,10 @@ class CheckSizeArgForCreate(argparse.Action):
|
|||||||
help='Creates volume from volume ID. Default=None.')
|
help='Creates volume from volume ID. Default=None.')
|
||||||
@utils.arg('--source_volid',
|
@utils.arg('--source_volid',
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
|
@utils.arg('--source-replica',
|
||||||
|
metavar='<source-replica>',
|
||||||
|
default=None,
|
||||||
|
help='Creates volume from replicated volume ID. Default=None.')
|
||||||
@utils.arg('--image-id',
|
@utils.arg('--image-id',
|
||||||
metavar='<image-id>',
|
metavar='<image-id>',
|
||||||
default=None,
|
default=None,
|
||||||
@@ -335,7 +340,8 @@ def do_create(cs, args):
|
|||||||
availability_zone=args.availability_zone,
|
availability_zone=args.availability_zone,
|
||||||
imageRef=args.image_id,
|
imageRef=args.image_id,
|
||||||
metadata=volume_metadata,
|
metadata=volume_metadata,
|
||||||
scheduler_hints=hints)
|
scheduler_hints=hints,
|
||||||
|
source_replica=args.source_replica)
|
||||||
|
|
||||||
info = dict()
|
info = dict()
|
||||||
volume = cs.volumes.get(volume.id)
|
volume = cs.volumes.get(volume.id)
|
||||||
@@ -1672,3 +1678,19 @@ def do_manage(cs, args):
|
|||||||
@utils.service_type('volumev2')
|
@utils.service_type('volumev2')
|
||||||
def do_unmanage(cs, args):
|
def do_unmanage(cs, args):
|
||||||
utils.find_volume(cs, args.volume).unmanage(args.volume)
|
utils.find_volume(cs, args.volume).unmanage(args.volume)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('volume', metavar='<volume>',
|
||||||
|
help='Name or ID of the volume to promote.')
|
||||||
|
@utils.service_type('volumev2')
|
||||||
|
def do_replication_promote(cs, args):
|
||||||
|
"""Promote a secondary volume to primary for a relationship."""
|
||||||
|
utils.find_volume(cs, args.volume).promote(args.volume)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('volume', metavar='<volume>',
|
||||||
|
help='Name or ID of the volume to reenable replication.')
|
||||||
|
@utils.service_type('volumev2')
|
||||||
|
def do_replication_reenable(cs, args):
|
||||||
|
"""Sync the secondary volume with primary for a relationship."""
|
||||||
|
utils.find_volume(cs, args.volume).reenable(args.volume)
|
||||||
|
@@ -152,6 +152,14 @@ class Volume(base.Resource):
|
|||||||
"""Unmanage a volume."""
|
"""Unmanage a volume."""
|
||||||
self.manager.unmanage(volume)
|
self.manager.unmanage(volume)
|
||||||
|
|
||||||
|
def promote(self, volume):
|
||||||
|
"""Promote secondary to be primary in relationship."""
|
||||||
|
self.manager.promote(volume)
|
||||||
|
|
||||||
|
def reenable(self, volume):
|
||||||
|
"""Sync the secondary volume with primary for a relationship."""
|
||||||
|
self.manager.reenable(volume)
|
||||||
|
|
||||||
|
|
||||||
class VolumeManager(base.ManagerWithFind):
|
class VolumeManager(base.ManagerWithFind):
|
||||||
"""Manage :class:`Volume` resources."""
|
"""Manage :class:`Volume` resources."""
|
||||||
@@ -161,7 +169,8 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
name=None, description=None,
|
name=None, description=None,
|
||||||
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):
|
||||||
"""Creates a volume.
|
"""Creates a volume.
|
||||||
|
|
||||||
:param size: Size of volume in GB
|
:param size: Size of volume in GB
|
||||||
@@ -175,6 +184,7 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
:param metadata: Optional metadata to set on volume creation
|
:param metadata: Optional metadata to set on volume creation
|
||||||
:param imageRef: reference to an image stored in glance
|
:param imageRef: reference to an image stored in glance
|
||||||
:param source_volid: ID of source volume to clone from
|
:param source_volid: ID of source volume to clone from
|
||||||
|
: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
|
||||||
:rtype: :class:`Volume`
|
:rtype: :class:`Volume`
|
||||||
@@ -198,6 +208,7 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
'metadata': volume_metadata,
|
'metadata': volume_metadata,
|
||||||
'imageRef': imageRef,
|
'imageRef': imageRef,
|
||||||
'source_volid': source_volid,
|
'source_volid': source_volid,
|
||||||
|
'source_replica': source_replica,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if scheduler_hints:
|
if scheduler_hints:
|
||||||
@@ -498,3 +509,11 @@ class VolumeManager(base.ManagerWithFind):
|
|||||||
def unmanage(self, volume):
|
def unmanage(self, volume):
|
||||||
"""Unmanage a volume."""
|
"""Unmanage a volume."""
|
||||||
return self._action('os-unmanage', volume, None)
|
return self._action('os-unmanage', volume, None)
|
||||||
|
|
||||||
|
def promote(self, volume):
|
||||||
|
"""Promote secondary to be primary in relationship."""
|
||||||
|
return self._action('os-promote-replica', volume, None)
|
||||||
|
|
||||||
|
def reenable(self, volume):
|
||||||
|
"""Sync the secondary volume with primary for a relationship."""
|
||||||
|
return self._action('os-reenable-replica', volume, None)
|
||||||
|
Reference in New Issue
Block a user