diff options
author | Andrey Kurilin <akurilin@mirantis.com> | 2016-02-10 16:05:48 +0200 |
---|---|---|
committer | Andrey Kurilin <akurilin@mirantis.com> | 2016-02-16 16:20:41 +0200 |
commit | 1d1e43957dc785231c50a105229814b06eb74752 (patch) | |
tree | 038b07488e1cd800d50556efbe01e1eb3e243c31 | |
parent | 99c588e28c2c4eb0b684cfd54d79d76fc30197fe (diff) | |
download | python-novaclient-1d1e43957dc785231c50a105229814b06eb74752.tar.gz |
[microversions] Add support for 2.19
2.19 - Allow the user to set and get the server description. The user will
be able to set the description when creating, rebuilding, or updating
a server, and get the description as part of the server details.
Methods `rebuild` and `create` of novaclient.v2.servers.ServerManager were
not wrapped with `api_versions.wraps` decorator to reduce code and docsting
duplication. Version checks added inside these methods.
Change-Id: I75b804c6edd0cdf02c2cd002d0b5968fec8da545
-rw-r--r-- | novaclient/__init__.py | 2 | ||||
-rw-r--r-- | novaclient/exceptions.py | 18 | ||||
-rw-r--r-- | novaclient/tests/functional/v2/test_servers.py | 35 | ||||
-rw-r--r-- | novaclient/tests/unit/v2/test_servers.py | 44 | ||||
-rw-r--r-- | novaclient/v2/servers.py | 61 | ||||
-rw-r--r-- | novaclient/v2/shell.py | 54 |
6 files changed, 206 insertions, 8 deletions
diff --git a/novaclient/__init__.py b/novaclient/__init__.py index d0859b11..e9b2a802 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.18") +API_MAX_VERSION = api_versions.APIVersion("2.19") diff --git a/novaclient/exceptions.py b/novaclient/exceptions.py index b9bdf114..c502e964 100644 --- a/novaclient/exceptions.py +++ b/novaclient/exceptions.py @@ -24,6 +24,24 @@ class UnsupportedVersion(Exception): pass +class UnsupportedAttribute(AttributeError): + """Indicates that the user is trying to transmit the argument to a method, + which is not supported by selected version. + """ + + def __init__(self, argument_name, start_version, end_version=None): + if end_version: + self.message = ( + "'%(name)s' argument is only allowed for microversions " + "%(start)s - %(end)s." % {"name": argument_name, + "start": start_version, + "end": end_version}) + else: + self.message = ( + "'%(name)s' argument is only allowed since microversion " + "%(start)s." % {"name": argument_name, "start": start_version}) + + class CommandError(Exception): pass diff --git a/novaclient/tests/functional/v2/test_servers.py b/novaclient/tests/functional/v2/test_servers.py index 590e543b..de5e57d9 100644 --- a/novaclient/tests/functional/v2/test_servers.py +++ b/novaclient/tests/functional/v2/test_servers.py @@ -53,3 +53,38 @@ class TestServerLockV29(base.ClientTestBase): self.nova("unlock %s" % server.id) self._show_server_and_check_lock_attr(server, False) + + +class TestServersDescription(base.ClientTestBase): + + COMPUTE_API_VERSION = "2.19" + + def _boot_server_with_description(self): + name = str(uuid.uuid4()) + network = self.client.networks.list()[0] + descr = "Some words about this test VM." + server = self.client.servers.create( + name, self.image, self.flavor, nics=[{"net-id": network.id}], + description=descr) + self.addCleanup(server.delete) + + self.assertEqual(descr, server.description) + + return server, descr + + def test_create(self): + server, descr = self._boot_server_with_description() + + output = self.nova("show %s" % server.id) + self.assertEqual(descr, self._get_value_from_the_table(output, + "description")) + + def test_update(self): + server, descr = self._boot_server_with_description() + + # remove description + self.nova("update %s --description ''" % server.id) + + output = self.nova("show %s" % server.id) + self.assertEqual("-", self._get_value_from_the_table(output, + "description")) diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 171a5dbc..b8a8bd16 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -945,6 +945,18 @@ class ServersTest(utils.FixturedTestCase): self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/os-interface/port-id') + def test_create_server_with_description(self): + self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.create, + name="My server", + description="descr", + image=1, + flavor=1, + meta={'foo': 'bar'}, + userdata="hello moto", + key_name="fakekey" + ) + class ServersV26Test(ServersTest): def setUp(self): @@ -1033,3 +1045,35 @@ class ServersV217Test(ServersV214Test): self.assert_called('POST', '/servers/1234/action') self.cs.servers.trigger_crash_dump(s) self.assert_called('POST', '/servers/1234/action') + + +class ServersV219Test(ServersV217Test): + def setUp(self): + super(ServersV219Test, self).setUp() + self.cs.api_version = api_versions.APIVersion("2.19") + + def test_create_server_with_description(self): + self.cs.servers.create( + name="My server", + description="descr", + image=1, + flavor=1, + meta={'foo': 'bar'}, + userdata="hello moto", + key_name="fakekey" + ) + self.assert_called('POST', '/servers') + + def test_update_server_with_description(self): + s = self.cs.servers.get(1234) + + s.update(description='hi') + s.update(name='hi', description='hi') + self.assert_called('PUT', '/servers/1234') + + def test_rebuild_with_description(self): + s = self.cs.servers.get(1234) + + ret = s.rebuild(image="1", description="descr") + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called('POST', '/servers/1234/action') diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index 7120df1a..961ae61d 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -28,6 +28,7 @@ from six.moves.urllib import parse from novaclient import api_versions from novaclient import base from novaclient import crypto +from novaclient import exceptions from novaclient.i18n import _ from novaclient.v2 import security_groups @@ -49,14 +50,22 @@ class Server(base.Resource): """ return self.manager.delete(self) - def update(self, name=None): + def update(self, name=None, description=None): """ - Update the name for this server. + Update the name and the description for this server. :param name: Update the server's name. + :param description: Update the server's description( + allowed for 2.19-latest). :returns: :class:`Server` """ - return self.manager.update(self, name=name) + if (description is not None and + self.manager.api_version < api_versions.APIVersion("2.19")): + raise exceptions.UnsupportedAttribute("description", "2.19") + update_kwargs = {"name": name} + if description is not None: + update_kwargs["description"] = description + return self.manager.update(self, **update_kwargs) def get_console_output(self, length=None): """ @@ -510,7 +519,8 @@ class ServerManager(base.BootingManagerWithFind): availability_zone=None, block_device_mapping=None, block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, admin_pass=None, disk_config=None, - access_ip_v4=None, access_ip_v6=None, **kwargs): + access_ip_v4=None, access_ip_v6=None, description=None, + **kwargs): """ Create (boot) a new server. """ @@ -632,6 +642,9 @@ class ServerManager(base.BootingManagerWithFind): if access_ip_v6 is not None: body['server']['accessIPv6'] = access_ip_v6 + if description: + body['server']['description'] = description + return self._create(resource_url, body, response_key, return_raw=return_raw, **kwargs) @@ -1168,6 +1181,8 @@ class ServerManager(base.BootingManagerWithFind): password. :param access_ip_v4: (optional extension) add alternative access ip v4 :param access_ip_v6: (optional extension) add alternative access ip v6 + :param description: optional description of the server (allowed since + microversion 2.19) """ if not min_count: min_count = 1 @@ -1178,6 +1193,10 @@ class ServerManager(base.BootingManagerWithFind): boot_args = [name, image, flavor] + descr_microversion = api_versions.APIVersion("2.19") + if "description" in kwargs and self.api_version < descr_microversion: + raise exceptions.UnsupportedAttribute("description", "2.19") + boot_kwargs = dict( meta=meta, files=files, userdata=userdata, reservation_id=reservation_id, min_count=min_count, @@ -1202,9 +1221,10 @@ class ServerManager(base.BootingManagerWithFind): return self._boot(resource_url, response_key, *boot_args, **boot_kwargs) + @api_versions.wraps("2.0", "2.18") def update(self, server, name=None): """ - Update the name or the password for a server. + Update the name for a server. :param server: The :class:`Server` (or its ID) to update. :param name: Update the server's name. @@ -1220,6 +1240,29 @@ class ServerManager(base.BootingManagerWithFind): return self._update("/servers/%s" % base.getid(server), body, "server") + @api_versions.wraps("2.19") + def update(self, server, name=None, description=None): + """ + Update the name or the description for a server. + + :param server: The :class:`Server` (or its ID) to update. + :param name: Update the server's name. + :param description: Update the server's description. If it equals to + empty string(i.g. ""), the server description will be removed. + """ + if name is None and description is None: + return + + body = {"server": {}} + if name: + body["server"]["name"] = name + if description == "": + body["server"]["description"] = None + elif description: + body["server"]["description"] = description + + return self._update("/servers/%s" % base.getid(server), body, "server") + def change_password(self, server, password): """ Update the password for a server. @@ -1271,8 +1314,14 @@ class ServerManager(base.BootingManagerWithFind): are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. + :param description: optional description of the server (allowed since + microversion 2.19) :returns: :class:`Server` """ + descr_microversion = api_versions.APIVersion("2.19") + if "description" in kwargs and self.api_version < descr_microversion: + raise exceptions.UnsupportedAttribute("description", "2.19") + body = {'imageRef': base.getid(image)} if password is not None: body['adminPass'] = password @@ -1282,6 +1331,8 @@ class ServerManager(base.BootingManagerWithFind): body['preserve_ephemeral'] = True if name is not None: body['name'] = name + if "description" in kwargs: + body["description"] = kwargs["description"] if meta: body['metadata'] = meta if files: diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index f16d5903..95b892d8 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -350,6 +350,9 @@ def _boot(cs, args): access_ip_v4=args.access_ip_v4, access_ip_v6=args.access_ip_v6) + if 'description' in args: + boot_kwargs["description"] = args.description + return boot_args, boot_kwargs @@ -569,6 +572,13 @@ def _boot(cs, args): metavar='<value>', default=None, help=_('Alternative access IPv6 of the instance.')) +@cliutils.arg( + '--description', + metavar='<description>', + dest='description', + default=None, + help=_('Description for the server.'), + start_version="2.19") def do_boot(cs, args): """Boot a new server.""" boot_args, boot_kwargs = _boot(cs, args) @@ -1625,6 +1635,13 @@ def do_reboot(cs, args): default=None, help=_('Name for the new server.')) @cliutils.arg( + '--description', + metavar='<description>', + dest='description', + default=None, + help=_('New description for the server.'), + start_version="2.19") +@cliutils.arg( '--meta', metavar="<key=value>", action='append', @@ -1652,6 +1669,8 @@ def do_rebuild(cs, args): kwargs = utils.get_resource_manager_extra_kwargs(do_rebuild, args) kwargs['preserve_ephemeral'] = args.preserve_ephemeral kwargs['name'] = args.name + if 'description' in args: + kwargs['description'] = args.description meta = _meta_parsing(args.meta) kwargs['meta'] = meta @@ -1682,8 +1701,39 @@ def do_rebuild(cs, args): help=_('Name (old name) or ID of server.')) @cliutils.arg('name', metavar='<name>', help=_('New name for the server.')) def do_rename(cs, args): - """Rename a server.""" - _find_server(cs, args.server).update(name=args.name) + """DEPRECATED, use update instead.""" + do_update(cs, args) + + +@cliutils.arg( + 'server', metavar='<server>', + help=_('Name (old name) or ID of server.')) +@cliutils.arg( + '--name', + metavar='<name>', + dest='name', + default=None, + help=_('New name for the server.')) +@cliutils.arg( + '--description', + metavar='<description>', + dest='description', + default=None, + help=_('New description for the server. If it equals to empty string ' + '(i.g. ""), the server description will be removed.'), + start_version="2.19") +def do_update(cs, args): + """Update the name or the description for a server.""" + update_kwargs = {} + if args.name: + update_kwargs["name"] = args.name + # NOTE(andreykurilin): `do_update` method is used by `do_rename` method, + # which do not have description argument at all. When `do_rename` will be + # removed after deprecation period, feel free to change the check below to: + # `if args.description:` + if "description" in args and args.description is not None: + update_kwargs["description"] = args.description + _find_server(cs, args.server).update(**update_kwargs) @cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.')) |