[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
This commit is contained in:
Andrey Kurilin 2016-02-10 16:05:48 +02:00
parent 99c588e28c
commit 1d1e43957d
6 changed files with 206 additions and 8 deletions

View File

@ -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")

View File

@ -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

View File

@ -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"))

View File

@ -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')

View File

@ -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:

View File

@ -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)
@ -1624,6 +1634,13 @@ def do_reboot(cs, args):
metavar='<name>',
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>",
@ -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.'))