Merge "Add client support in Cinder for volume replication"

This commit is contained in:
Jenkins
2014-09-05 03:32:33 +00:00
committed by Gerrit Code Review
5 changed files with 74 additions and 4 deletions

View File

@@ -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, {}, {})

View File

@@ -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})

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)