From 6c398058a6234e3a59822bbcd9ca5d3d05107e96 Mon Sep 17 00:00:00 2001 From: Yikun Jiang Date: Wed, 27 Jun 2018 11:52:04 +0800 Subject: [PATCH] Microversion 2.64 - Use new format policy in server group Added support for microversion 2.64, which includes the following changes: - The ``--rule`` option is added to the ``nova server-group-create`` CLI that enables user to create server group with specific policy rules. - Remove ``metadata`` column in the output of ``nova server-group-create``, ``nova server-group-get``, ``nova server-group-list``. - Remove ``policies`` column, , add ``policy`` and ``rules`` columns in the output of ``nova server-group-create``, ``nova server-group-get``, ``nova server-group-list``. Depends-On: 3cd26f1e68b09ba7925e794ac8912566c239b6df blueprint: complex-anti-affinity-policies Change-Id: I903f4b5544806b9d3c8bac529448abbc9dd3cee9 --- doc/source/cli/nova.rst | 12 +++- novaclient/__init__.py | 2 +- .../tests/functional/v2/test_server_groups.py | 69 ++++++++++++++++++- novaclient/tests/unit/v2/fakes.py | 9 ++- .../tests/unit/v2/test_server_groups.py | 34 +++++++++ novaclient/tests/unit/v2/test_shell.py | 45 ++++++++++++ novaclient/v2/server_groups.py | 30 ++++++++ novaclient/v2/shell.py | 42 ++++++++--- ...roversion-v2_64-66366829ec65bea4.yaml.yaml | 15 ++++ 9 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index cd6d60eeb..34b9e674c 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -2951,7 +2951,7 @@ nova server-group-create .. code-block:: console - usage: nova server-group-create + usage: nova server-group-create [--rules ] Create a new server group with the specified details. @@ -2961,7 +2961,15 @@ Create a new server group with the specified details. Server group name. ```` - Policies for the server groups. + Policy for the server groups. + +``--rule`` + Policy rules for the server groups. (Supported by API versions + '2.64' - '2.latest'). Currently, only the ``max_server_per_host`` rule + is supported for the ``anti-affinity`` policy. The ``max_server_per_host`` + rule allows specifying how many members of the anti-affinity group can + reside on the same compute host. If not specified, only one member from + the same anti-affinity group can reside on a given host. .. _nova_server-group-delete: diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 3bcad2062..1923e6a12 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.63") +API_MAX_VERSION = api_versions.APIVersion("2.64") diff --git a/novaclient/tests/functional/v2/test_server_groups.py b/novaclient/tests/functional/v2/test_server_groups.py index b2ad1262e..37ebaa66d 100644 --- a/novaclient/tests/functional/v2/test_server_groups.py +++ b/novaclient/tests/functional/v2/test_server_groups.py @@ -17,7 +17,9 @@ from novaclient.tests.functional.v2.legacy import test_server_groups class TestServerGroupClientV213(test_server_groups.TestServerGroupClient): """Server groups v2.13 functional tests.""" - COMPUTE_API_VERSION = "2.latest" + COMPUTE_API_VERSION = "2.13" + expected_metadata = True + expected_policy_rules = False def test_create_server_group(self): sg_id = self._create_sg("affinity") @@ -29,6 +31,11 @@ class TestServerGroupClientV213(test_server_groups.TestServerGroupClient): self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) + self._get_column_value_from_single_row_table(sg, "Metadata") + self.assertIn( + 'affinity', + self._get_column_value_from_single_row_table(sg, 'Policies')) + self.assertNotIn('Rules', sg) def test_list_server_groups(self): sg_id = self._create_sg("affinity") @@ -40,6 +47,22 @@ class TestServerGroupClientV213(test_server_groups.TestServerGroupClient): self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) + if self.expected_metadata: + self._get_column_value_from_single_row_table(sg, "Metadata") + else: + self.assertNotIn(sg, 'Metadata') + if self.expected_policy_rules: + self.assertEqual( + 'affinity', + self._get_column_value_from_single_row_table(sg, "Policy")) + self.assertEqual( + '{}', + self._get_column_value_from_single_row_table(sg, "Rules")) + else: + self.assertIn( + 'affinity', + self._get_column_value_from_single_row_table(sg, 'Policies')) + self.assertNotIn('Rules', sg) def test_get_server_group(self): sg_id = self._create_sg("affinity") @@ -51,3 +74,47 @@ class TestServerGroupClientV213(test_server_groups.TestServerGroupClient): self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) + if self.expected_metadata: + self._get_column_value_from_single_row_table(sg, "Metadata") + else: + self.assertNotIn(sg, 'Metadata') + if self.expected_policy_rules: + self.assertEqual( + 'affinity', + self._get_column_value_from_single_row_table(sg, "Policy")) + self.assertEqual( + '{}', + self._get_column_value_from_single_row_table(sg, "Rules")) + else: + self.assertIn( + 'affinity', + self._get_column_value_from_single_row_table(sg, 'Policies')) + self.assertNotIn('Rules', sg) + + +class TestServerGroupClientV264(TestServerGroupClientV213): + """Server groups v2.64 functional tests.""" + + COMPUTE_API_VERSION = "2.64" + expected_metadata = False + expected_policy_rules = True + + def test_create_server_group(self): + output = self.nova('server-group-create complex-anti-affinity-group ' + 'anti-affinity --rule max_server_per_host=3') + sg_id = self._get_column_value_from_single_row_table(output, "Id") + self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) + sg = self.nova('server-group-get %s' % sg_id) + result = self._get_column_value_from_single_row_table(sg, "Id") + self.assertEqual(sg_id, result) + self._get_column_value_from_single_row_table( + sg, "User Id") + self._get_column_value_from_single_row_table( + sg, "Project Id") + self.assertNotIn('Metadata', sg) + self.assertEqual( + 'anti-affinity', + self._get_column_value_from_single_row_table(sg, "Policy")) + self.assertIn( + 'max_server_per_host', + self._get_column_value_from_single_row_table(sg, "Rules")) diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 1d6f3f326..374648be3 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -2236,8 +2236,13 @@ class FakeSessionClient(base_client.SessionClient): return (200, {}, {"server_groups": server_groups}) def _return_server_group(self): - r = {'server_group': - self.get_os_server_groups()[2]['server_groups'][0]} + if self.api_version < api_versions.APIVersion("2.64"): + r = {'server_group': + self.get_os_server_groups()[2]['server_groups'][0]} + else: + r = {"members": [], "id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b", + 'server_group': {'name': 'ig1', 'policy': 'anti-affinity', + 'rules': {'max_server_per_host': 3}}} return (200, {}, r) def post_os_server_groups(self, body, **kw): diff --git a/novaclient/tests/unit/v2/test_server_groups.py b/novaclient/tests/unit/v2/test_server_groups.py index 9881b20aa..40af1a13e 100644 --- a/novaclient/tests/unit/v2/test_server_groups.py +++ b/novaclient/tests/unit/v2/test_server_groups.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import server_groups as data @@ -106,3 +107,36 @@ class ServerGroupsTest(utils.FixturedTestCase): self.cs.server_groups.find, **kwargs) self.assert_called('GET', '/os-server-groups') + + +class ServerGroupsTestV264(ServerGroupsTest): + def setUp(self): + super(ServerGroupsTestV264, self).setUp() + self.cs.api_version = api_versions.APIVersion("2.64") + + def test_create_server_group(self): + name = 'ig1' + policy = 'anti-affinity' + server_group = self.cs.server_groups.create(name, policy) + self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) + body = {'server_group': {'name': name, 'policy': policy}} + self.assert_called('POST', '/os-server-groups', body) + self.assertIsInstance(server_group, + server_groups.ServerGroup) + + def test_create_server_group_with_rules(self): + kwargs = {'name': 'ig1', + 'policy': 'anti-affinity', + 'rules': {'max_server_per_host': 3}} + server_group = self.cs.server_groups.create(**kwargs) + self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) + body = { + 'server_group': { + 'name': 'ig1', + 'policy': 'anti-affinity', + 'rules': {'max_server_per_host': 3} + } + } + self.assert_called('POST', '/os-server-groups', body) + self.assertIsInstance(server_group, + server_groups.ServerGroup) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index a32d623b8..b748dc6eb 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -3728,6 +3728,48 @@ class ShellTest(utils.TestCase): {'server_group': {'name': 'wjsg', 'policies': ['affinity']}}) + def test_create_server_group_v2_64(self): + self.run_command('server-group-create sg1 affinity', + api_version='2.64') + self.assert_called('POST', '/os-server-groups', + {'server_group': { + 'name': 'sg1', + 'policy': 'affinity' + }}) + + def test_create_server_group_with_rules(self): + self.run_command('server-group-create sg1 anti-affinity ' + '--rule max_server_per_host=3', api_version='2.64') + self.assert_called('POST', '/os-server-groups', + {'server_group': { + 'name': 'sg1', + 'policy': 'anti-affinity', + 'rules': {'max_server_per_host': 3} + }}) + + def test_create_server_group_with_multi_rules(self): + self.run_command('server-group-create sg1 anti-affinity ' + '--rule a=b --rule c=d', api_version='2.64') + self.assert_called('POST', '/os-server-groups', + {'server_group': { + 'name': 'sg1', + 'policy': 'anti-affinity', + 'rules': {'a': 'b', 'c': 'd'} + }}) + + def test_create_server_group_with_invalid_value(self): + result = self.assertRaises( + exceptions.CommandError, self.run_command, + 'server-group-create sg1 anti-affinity ' + '--rule max_server_per_host=foo', api_version='2.64') + self.assertIn("Invalid 'max_server_per_host' value: foo", + six.text_type(result)) + + def test_create_server_group_with_rules_pre_264(self): + self.assertRaises(SystemExit, self.run_command, + 'server-group-create sg1 anti-affinity ' + '--rule max_server_per_host=3', api_version='2.63') + def test_create_server_group_with_multiple_policies(self): self.assertRaises(SystemExit, self.run_command, 'server-group-create wjsg affinity anti-affinity') @@ -3758,6 +3800,9 @@ class ShellTest(utils.TestCase): 7, # doesn't require any changes in novaclient 9, # doesn't require any changes in novaclient 12, # no longer supported + 13, # 13 adds information ``project_id`` and ``user_id`` to + # ``os-server-groups``, but is not explicitly tested + # via wraps and _SUBSTITUTIONS. 15, # doesn't require any changes in novaclient 16, # doesn't require any changes in novaclient 18, # NOTE(andreykurilin): this microversion requires changes in diff --git a/novaclient/v2/server_groups.py b/novaclient/v2/server_groups.py index 62a5c9a09..e8839ae59 100644 --- a/novaclient/v2/server_groups.py +++ b/novaclient/v2/server_groups.py @@ -17,7 +17,10 @@ Server group interface. """ +from novaclient import api_versions from novaclient import base +from novaclient import exceptions +from novaclient.i18n import _ class ServerGroup(base.Resource): @@ -80,6 +83,7 @@ class ServerGroupsManager(base.ManagerWithFind): """ return self._delete('/os-server-groups/%s' % id) + @api_versions.wraps("2.0", "2.63") def create(self, name, policies): """Create (allocate) a server group. @@ -92,3 +96,29 @@ class ServerGroupsManager(base.ManagerWithFind): body = {'server_group': {'name': name, 'policies': policies}} return self._create('/os-server-groups', body, 'server_group') + + @api_versions.wraps("2.64") + def create(self, name, policy, rules=None): + """Create (allocate) a server group. + + :param name: The name of the server group. + :param policy: Policy name to associate with the server group. + :param rules: The rules of policy which is a dict, can be applied to + the policy, now only ``max_server_per_host`` for ``anti-affinity`` + policy would be supported (optional). + :rtype: list of :class:`ServerGroup` + """ + body = {'server_group': { + 'name': name, 'policy': policy + }} + if rules: + key = 'max_server_per_host' + try: + if key in rules: + rules[key] = int(rules[key]) + except ValueError: + msg = _("Invalid '%(key)s' value: %(value)s") + raise exceptions.CommandError(msg % { + 'key': key, 'value': rules[key]}) + body['server_group']['rules'] = rules + return self._create('/os-server-groups', body, 'server_group') diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 99cc2f5a3..0b4450bd0 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -4525,16 +4525,15 @@ def do_availability_zone_list(cs, _args): sortby_index=None) -@api_versions.wraps("2.0", "2.12") def _print_server_group_details(cs, server_group): - columns = ['Id', 'Name', 'Policies', 'Members', 'Metadata'] - utils.print_list(server_group, columns) - - -@api_versions.wraps("2.13") -def _print_server_group_details(cs, server_group): # noqa - columns = ['Id', 'Name', 'Project Id', 'User Id', - 'Policies', 'Members', 'Metadata'] + if cs.api_version < api_versions.APIVersion('2.13'): + columns = ['Id', 'Name', 'Policies', 'Members', 'Metadata'] + elif cs.api_version < api_versions.APIVersion('2.64'): + columns = ['Id', 'Name', 'Project Id', 'User Id', + 'Policies', 'Members', 'Metadata'] + else: + columns = ['Id', 'Name', 'Project Id', 'User Id', + 'Policy', 'Rules', 'Members'] utils.print_list(server_group, columns) @@ -4569,6 +4568,7 @@ def do_server_group_list(cs, args): _print_server_group_details(cs, server_groups) +@api_versions.wraps("2.0", "2.63") @utils.arg('name', metavar='', help=_('Server group name.')) @utils.arg( 'policy', @@ -4581,6 +4581,30 @@ def do_server_group_create(cs, args): _print_server_group_details(cs, [server_group]) +@api_versions.wraps("2.64") +@utils.arg('name', metavar='', help=_('Server group name.')) +@utils.arg( + 'policy', + metavar='', + help=_('Policy for the server group.')) +@utils.arg( + '--rule', + metavar="", + dest='rules', + action='append', + default=[], + help=_('A rule for the policy. Currently, only the ' + '``max_server_per_host`` rule is supported for the ' + '``anti-affinity`` policy.')) +def do_server_group_create(cs, args): + """Create a new server group with the specified details.""" + rules = _meta_parsing(args.rules) + server_group = cs.server_groups.create(name=args.name, + policy=args.policy, + rules=rules) + _print_server_group_details(cs, [server_group]) + + @utils.arg( 'id', metavar='', diff --git a/releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml b/releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml new file mode 100644 index 000000000..0bd5d4a56 --- /dev/null +++ b/releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Added support for `microversion 2.64`_, which includes the following + changes: + + * The ``--rule`` options is added to the ``nova server-group-create`` + CLI that enables user to create server group with specific policy rules. + * Remove ``metadata`` column in the output of ``nova server-group-create``, + ``nova server-group-get``, ``nova server-group-list``. + * Remove ``policies`` column, add ``policy`` and ``rules`` columns in + the output of ``nova server-group-create``, ``nova server-group-get``, + ``nova server-group-list``. + + .. _microversion 2.64: https://docs.openstack.org/nova/latest/api_microversion_history.html#id58