Tiramisu: replication group support

This patch adds CLI support for replication group.
It is built upon the generic volume groups.

Server side patch is here:
    https://review.openstack.org/#/c/352228/

Depends-On: I4d488252bd670b3ebabbcc9f5e29e0e4e913765a
Change-Id: I462c3ab8c9c3a6a1b434748f81d208359ffd2431
Implements: blueprint replication-cg
This commit is contained in:
xing-yang
2016-07-09 21:56:42 -04:00
parent 0fdd41d84c
commit da79866e14
6 changed files with 236 additions and 1 deletions

View File

@@ -369,6 +369,9 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
action = list(body)[0]
if action == 'delete':
assert 'delete-volumes' in body[action]
elif action in ('enable_replication', 'disable_replication',
'failover_replication', 'list_replication_targets'):
assert action in body
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, {})

View File

@@ -158,3 +158,57 @@ class GroupsTest(utils.TestCase):
cs.assert_called('POST', '/groups/action',
body=expected)
self._assert_request_id(grp)
def test_enable_replication_group(self):
expected = {'enable_replication': {}}
g0 = cs.groups.list()[0]
grp = g0.enable_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.enable_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.enable_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_disable_replication_group(self):
expected = {'disable_replication': {}}
g0 = cs.groups.list()[0]
grp = g0.disable_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.disable_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.disable_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_failover_replication_group(self):
expected = {'failover_replication':
{'allow_attached_volume': False,
'secondary_backend_id': None}}
g0 = cs.groups.list()[0]
grp = g0.failover_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.failover_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.failover_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_list_replication_targets(self):
expected = {'list_replication_targets': {}}
g0 = cs.groups.list()[0]
grp = g0.list_replication_targets()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.list_replication_targets('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.list_replication_targets(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)

View File

@@ -754,3 +754,37 @@ class ShellTest(utils.TestCase):
command += ' --withreplication %s' % replication
self.run_command(command)
self.assert_called('GET', '/os-services')
def test_group_enable_replication(self):
cmd = '--os-volume-api-version 3.38 group-enable-replication 1234'
self.run_command(cmd)
expected = {'enable_replication': {}}
self.assert_called('POST', '/groups/1234/action', body=expected)
def test_group_disable_replication(self):
cmd = '--os-volume-api-version 3.38 group-disable-replication 1234'
self.run_command(cmd)
expected = {'disable_replication': {}}
self.assert_called('POST', '/groups/1234/action', body=expected)
@ddt.data((False, None), (True, None),
(False, "backend1"), (True, "backend1"),
(False, "default"), (True, "default"))
@ddt.unpack
def test_group_failover_replication(self, attach_vol, backend):
attach = '--allow-attached-volume ' if attach_vol else ''
backend_id = ('--secondary-backend-id ' + backend) if backend else ''
cmd = ('--os-volume-api-version 3.38 group-failover-replication 1234 '
+ attach + backend_id)
self.run_command(cmd)
expected = {'failover_replication':
{'allow_attached_volume': attach_vol,
'secondary_backend_id': backend if backend else None}}
self.assert_called('POST', '/groups/1234/action', body=expected)
def test_group_list_replication_targets(self):
cmd = ('--os-volume-api-version 3.38 group-list-replication-targets'
' 1234')
self.run_command(cmd)
expected = {'list_replication_targets': {}}
self.assert_called('POST', '/groups/1234/action', body=expected)

View File

@@ -39,6 +39,25 @@ class Group(base.Resource):
"""Reset the group's state with specified one"""
return self.manager.reset_state(self, state)
def enable_replication(self):
"""Enables replication for this group."""
return self.manager.enable_replication(self)
def disable_replication(self):
"""Disables replication for this group."""
return self.manager.disable_replication(self)
def failover_replication(self, allow_attached_volume=False,
secondary_backend_id=None):
"""Fails over replication for this group."""
return self.manager.failover_replication(self,
allow_attached_volume,
secondary_backend_id)
def list_replication_targets(self):
"""Lists replication targets for this group."""
return self.manager.list_replication_targets(self)
class GroupManager(base.ManagerWithFind):
"""Manage :class:`Group` resources."""
@@ -180,3 +199,55 @@ class GroupManager(base.ManagerWithFind):
url = '/groups/%s/action' % base.getid(group)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def enable_replication(self, group):
"""Enables replication for a group.
:param group: the :class:`Group` to enable replication.
"""
body = {'enable_replication': {}}
self.run_hooks('modify_body_for_action', body, 'group')
url = '/groups/%s/action' % base.getid(group)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def disable_replication(self, group):
"""disables replication for a group.
:param group: the :class:`Group` to disable replication.
"""
body = {'disable_replication': {}}
self.run_hooks('modify_body_for_action', body, 'group')
url = '/groups/%s/action' % base.getid(group)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def failover_replication(self, group, allow_attached_volume=False,
secondary_backend_id=None):
"""fails over replication for a group.
:param group: the :class:`Group` to failover.
:param allow attached volumes: allow attached volumes in the group.
:param secondary_backend_id: secondary backend id.
"""
body = {
'failover_replication': {
'allow_attached_volume': allow_attached_volume,
'secondary_backend_id': secondary_backend_id
}
}
self.run_hooks('modify_body_for_action', body, 'group')
url = '/groups/%s/action' % base.getid(group)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def list_replication_targets(self, group):
"""List replication targets for a group.
:param group: the :class:`Group` to list replication targets.
"""
body = {'list_replication_targets': {}}
self.run_hooks('modify_body_for_action', body, 'group')
url = '/groups/%s/action' % base.getid(group)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)

View File

@@ -1174,7 +1174,74 @@ def do_group_update(cs, args):
print("Request to update group '%s' has been accepted." % args.group)
@api_versions.wraps('3.14')
@api_versions.wraps('3.38')
@utils.arg('group',
metavar='<group>',
help='Name or ID of the group.')
def do_group_enable_replication(cs, args):
"""Enables replication for group."""
shell_utils.find_group(cs, args.group).enable_replication()
@api_versions.wraps('3.38')
@utils.arg('group',
metavar='<group>',
help='Name or ID of the group.')
def do_group_disable_replication(cs, args):
"""Disables replication for group."""
shell_utils.find_group(cs, args.group).disable_replication()
@api_versions.wraps('3.38')
@utils.arg('group',
metavar='<group>',
help='Name or ID of the group.')
@utils.arg('--allow-attached-volume',
action='store_true',
default=False,
help='Allows or disallows group with '
'attached volumes to be failed over.')
@utils.arg('--secondary-backend-id',
metavar='<secondary_backend_id>',
help='Secondary backend id. Default=None.')
def do_group_failover_replication(cs, args):
"""Fails over replication for group."""
shell_utils.find_group(cs, args.group).failover_replication(
allow_attached_volume=args.allow_attached_volume,
secondary_backend_id=args.secondary_backend_id)
@api_versions.wraps('3.38')
@utils.arg('group',
metavar='<group>',
help='Name or ID of the group.')
def do_group_list_replication_targets(cs, args):
"""Lists replication targets for group.
Example value for replication_targets:
.. code-block: json
{
'replication_targets': [{'backend_id': 'vendor-id-1',
'unique_key': 'val1',
......},
{'backend_id': 'vendor-id-2',
'unique_key': 'val2',
......}]
}
"""
rc, replication_targets = shell_utils.find_group(
cs, args.group).list_replication_targets()
rep_targets = replication_targets.get('replication_targets')
if rep_targets and len(rep_targets) > 0:
utils.print_list(rep_targets, [key for key in rep_targets[0].keys()])
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',

View File

@@ -0,0 +1,6 @@
---
features:
- |
Added support for replication group APIs ``enable_replication``,
``disable_replication``, ``failover_replication`` and
``list_replication_targets``.