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']
|
||||
elif action == 'os-unmanage':
|
||||
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:
|
||||
raise AssertionError("Unexpected action: %s" % action)
|
||||
return (resp, {}, _body)
|
||||
@@ -835,3 +839,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
volume = _stub_volume(id='1234')
|
||||
volume.update(kw['body']['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('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):
|
||||
self.assertRaises(SystemExit, self.run_command, 'create')
|
||||
|
||||
@@ -522,3 +530,13 @@ class ShellTest(utils.TestCase):
|
||||
self.run_command('unmanage 1234')
|
||||
self.assert_called('POST', '/volumes/1234/action',
|
||||
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',
|
||||
'volume_type': None,
|
||||
'project_id': None,
|
||||
'metadata': {}},
|
||||
'metadata': {},
|
||||
'source_replica': None},
|
||||
'OS-SCH-HNT:scheduler_hints': 'uuid'}
|
||||
cs.assert_called('POST', '/volumes', body=expected)
|
||||
|
||||
|
@@ -226,7 +226,8 @@ def do_show(cs, args):
|
||||
|
||||
class CheckSizeArgForCreate(argparse.Action):
|
||||
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 '
|
||||
'or source volume is not specified.')
|
||||
setattr(args, self.dest, values)
|
||||
@@ -251,6 +252,10 @@ class CheckSizeArgForCreate(argparse.Action):
|
||||
help='Creates volume from volume ID. Default=None.')
|
||||
@utils.arg('--source_volid',
|
||||
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',
|
||||
metavar='<image-id>',
|
||||
default=None,
|
||||
@@ -335,7 +340,8 @@ def do_create(cs, args):
|
||||
availability_zone=args.availability_zone,
|
||||
imageRef=args.image_id,
|
||||
metadata=volume_metadata,
|
||||
scheduler_hints=hints)
|
||||
scheduler_hints=hints,
|
||||
source_replica=args.source_replica)
|
||||
|
||||
info = dict()
|
||||
volume = cs.volumes.get(volume.id)
|
||||
@@ -1672,3 +1678,19 @@ def do_manage(cs, args):
|
||||
@utils.service_type('volumev2')
|
||||
def do_unmanage(cs, args):
|
||||
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."""
|
||||
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):
|
||||
"""Manage :class:`Volume` resources."""
|
||||
@@ -161,7 +169,8 @@ class VolumeManager(base.ManagerWithFind):
|
||||
name=None, description=None,
|
||||
volume_type=None, user_id=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.
|
||||
|
||||
: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 imageRef: reference to an image stored in glance
|
||||
: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
|
||||
specified by the client to help boot an instance
|
||||
:rtype: :class:`Volume`
|
||||
@@ -198,6 +208,7 @@ class VolumeManager(base.ManagerWithFind):
|
||||
'metadata': volume_metadata,
|
||||
'imageRef': imageRef,
|
||||
'source_volid': source_volid,
|
||||
'source_replica': source_replica,
|
||||
}}
|
||||
|
||||
if scheduler_hints:
|
||||
@@ -498,3 +509,11 @@ class VolumeManager(base.ManagerWithFind):
|
||||
def unmanage(self, volume):
|
||||
"""Unmanage a volume."""
|
||||
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