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:
@@ -369,6 +369,9 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
|
|||||||
action = list(body)[0]
|
action = list(body)[0]
|
||||||
if action == 'delete':
|
if action == 'delete':
|
||||||
assert 'delete-volumes' in body[action]
|
assert 'delete-volumes' in body[action]
|
||||||
|
elif action in ('enable_replication', 'disable_replication',
|
||||||
|
'failover_replication', 'list_replication_targets'):
|
||||||
|
assert action in body
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Unexpected action: %s" % action)
|
raise AssertionError("Unexpected action: %s" % action)
|
||||||
return (resp, {}, {})
|
return (resp, {}, {})
|
||||||
|
@@ -158,3 +158,57 @@ class GroupsTest(utils.TestCase):
|
|||||||
cs.assert_called('POST', '/groups/action',
|
cs.assert_called('POST', '/groups/action',
|
||||||
body=expected)
|
body=expected)
|
||||||
self._assert_request_id(grp)
|
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)
|
||||||
|
@@ -754,3 +754,37 @@ class ShellTest(utils.TestCase):
|
|||||||
command += ' --withreplication %s' % replication
|
command += ' --withreplication %s' % replication
|
||||||
self.run_command(command)
|
self.run_command(command)
|
||||||
self.assert_called('GET', '/os-services')
|
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)
|
||||||
|
@@ -39,6 +39,25 @@ class Group(base.Resource):
|
|||||||
"""Reset the group's state with specified one"""
|
"""Reset the group's state with specified one"""
|
||||||
return self.manager.reset_state(self, state)
|
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):
|
class GroupManager(base.ManagerWithFind):
|
||||||
"""Manage :class:`Group` resources."""
|
"""Manage :class:`Group` resources."""
|
||||||
@@ -180,3 +199,55 @@ class GroupManager(base.ManagerWithFind):
|
|||||||
url = '/groups/%s/action' % base.getid(group)
|
url = '/groups/%s/action' % base.getid(group)
|
||||||
resp, body = self.api.client.post(url, body=body)
|
resp, body = self.api.client.post(url, body=body)
|
||||||
return common_base.TupleWithMeta((resp, body), resp)
|
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)
|
||||||
|
@@ -1174,7 +1174,74 @@ def do_group_update(cs, args):
|
|||||||
print("Request to update group '%s' has been accepted." % args.group)
|
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',
|
@utils.arg('--all-tenants',
|
||||||
dest='all_tenants',
|
dest='all_tenants',
|
||||||
metavar='<0|1>',
|
metavar='<0|1>',
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for replication group APIs ``enable_replication``,
|
||||||
|
``disable_replication``, ``failover_replication`` and
|
||||||
|
``list_replication_targets``.
|
Reference in New Issue
Block a user