diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 25a815136..7b6ea7149 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -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, {}, {}) diff --git a/cinderclient/tests/unit/v3/test_groups.py b/cinderclient/tests/unit/v3/test_groups.py index 72e72a006..d74e2a075 100644 --- a/cinderclient/tests/unit/v3/test_groups.py +++ b/cinderclient/tests/unit/v3/test_groups.py @@ -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) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 977754866..bfd2d93c9 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -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) diff --git a/cinderclient/v3/groups.py b/cinderclient/v3/groups.py index 386a6a307..6bc298a25 100644 --- a/cinderclient/v3/groups.py +++ b/cinderclient/v3/groups.py @@ -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) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 8994906d8..3b73b159d 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -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='', + 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='', + 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='', + 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='', + 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='', + 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>', diff --git a/releasenotes/notes/replication-group-v3-api-022900ce6bf8feba.yaml b/releasenotes/notes/replication-group-v3-api-022900ce6bf8feba.yaml new file mode 100644 index 000000000..43e0cd001 --- /dev/null +++ b/releasenotes/notes/replication-group-v3-api-022900ce6bf8feba.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support for replication group APIs ``enable_replication``, + ``disable_replication``, ``failover_replication`` and + ``list_replication_targets``.