Microversion 2.90 - Configurable hostnames

Change-Id: Icd4362a07196e59bafcdfaff44323ce1386d4f55
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Depends-On: https://review.opendev.org/c/openstack/nova/+/778550/
This commit is contained in:
Stephen Finucane 2021-08-31 17:44:20 +01:00
parent 01c7a3aa10
commit 8066f8c745
7 changed files with 333 additions and 38 deletions

View File

@ -975,6 +975,7 @@ nova boot
[--trusted-image-certificate-id <trusted-image-certificate-id>] [--trusted-image-certificate-id <trusted-image-certificate-id>]
[--host <host>] [--host <host>]
[--hypervisor-hostname <hypervisor-hostname>] [--hypervisor-hostname <hypervisor-hostname>]
[--hostname <hostname>]
<name> <name>
Boot a new server. Boot a new server.
@ -1149,6 +1150,12 @@ quality of service support, microversion ``2.72`` is required.
Requested hypervisor hostname to create servers. Admin only by default. Requested hypervisor hostname to create servers. Admin only by default.
(Supported by API versions '2.74' - '2.latest') (Supported by API versions '2.74' - '2.latest')
``--hostname <hostname>``
Hostname for the instance. This sets the hostname stored in the
metadata server: a utility such as cloud-init running on the guest
is required to propagate these changes to the guest.
(Supported by API versions '2.90' - '2.latest')
.. _nova_clear-password: .. _nova_clear-password:
nova clear-password nova clear-password
@ -2885,6 +2892,7 @@ nova rebuild
[--user-data <user-data>] [--user-data-unset] [--user-data <user-data>] [--user-data-unset]
[--trusted-image-certificate-id <trusted-image-certificate-id>] [--trusted-image-certificate-id <trusted-image-certificate-id>]
[--trusted-image-certificates-unset] [--trusted-image-certificates-unset]
[--hostname <hostname>]
<server> <image> <server> <image>
Shutdown, re-image, and re-boot a server. Shutdown, re-image, and re-boot a server.
@ -2958,6 +2966,12 @@ Shutdown, re-image, and re-boot a server.
specified with the ``--trusted-image-certificate-id`` option. specified with the ``--trusted-image-certificate-id`` option.
(Supported by API versions '2.63' - '2.latest') (Supported by API versions '2.63' - '2.latest')
``--hostname <hostname>``
New hostname for the instance. This only updates the hostname
stored in the metadata server: a utility running on the guest
is required to propagate these changes to the guest.
(Supported by API versions '2.90' - '2.latest')
.. _nova_refresh-network: .. _nova_refresh-network:
nova refresh-network nova refresh-network
@ -3795,9 +3809,11 @@ nova update
.. code-block:: console .. code-block:: console
usage: nova update [--name <name>] [--description <description>] <server> usage: nova update [--name <name>] [--description <description>]
[--hostname <hostname>]
<server>
Update the name or the description for a server. Update attributes of a server.
**Positional arguments:** **Positional arguments:**
@ -3815,6 +3831,12 @@ Update the name or the description for a server.
will be removed. (Supported by API versions will be removed. (Supported by API versions
'2.19' - '2.latest') '2.19' - '2.latest')
``--hostname <hostname>``
New hostname for the instance. This only updates the hostname
stored in the metadata server: a utility running on the guest
is required to propagate these changes to the guest.
(Supported by API versions '2.90' - '2.latest')
.. _nova_usage: .. _nova_usage:
nova usage nova usage

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise # when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some # the client may break due to server side new version may include some
# backward incompatible change. # backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.89") API_MAX_VERSION = api_versions.APIVersion("2.90")

View File

@ -1905,3 +1905,90 @@ class ServersV278Test(ServersV273Test):
self.cs.api_version = api_versions.APIVersion('2.77') self.cs.api_version = api_versions.APIVersion('2.77')
s = self.cs.servers.get(1234) s = self.cs.servers.get(1234)
self.assertRaises(exceptions.VersionNotFoundForAPIMethod, s.topology) self.assertRaises(exceptions.VersionNotFoundForAPIMethod, s.topology)
class ServersV290Test(ServersV278Test):
api_version = '2.90'
def test_create_server_with_hostname(self):
self.cs.servers.create(
name='My server',
image=1,
flavor=1,
nics='auto',
hostname='new-hostname',
)
self.assert_called(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'imageRef': '1',
'max_count': 1,
'min_count': 1,
'name': 'My server',
'networks': 'auto',
'hostname': 'new-hostname'
},
}
)
def test_create_server_with_hostname_pre_290_fails(self):
self.cs.api_version = api_versions.APIVersion('2.89')
ex = self.assertRaises(
exceptions.UnsupportedAttribute,
self.cs.servers.create,
name='My server',
image=1,
flavor=1,
nics='auto',
hostname='new-hostname')
self.assertIn(
"'hostname' argument is only allowed since microversion 2.90",
str(ex))
def test_rebuild_server_with_hostname(self):
s = self.cs.servers.get(1234)
ret = s.rebuild(image="1", hostname='new-hostname')
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called(
'POST', '/servers/1234/action',
{
'rebuild': {
'imageRef': '1',
'hostname': 'new-hostname',
},
},
)
def test_rebuild_server_with_hostname_pre_290_fails(self):
self.cs.api_version = api_versions.APIVersion('2.89')
ex = self.assertRaises(
exceptions.UnsupportedAttribute,
self.cs.servers.rebuild,
'1234', fakes.FAKE_IMAGE_UUID_1,
hostname='new-hostname')
self.assertIn('hostname', str(ex))
def test_update_server_with_hostname(self):
s = self.cs.servers.get(1234)
s.update(hostname='new-hostname')
self.assert_called(
'PUT', '/servers/1234',
{
'server': {
'hostname': 'new-hostname',
},
},
)
def test_update_with_hostname_pre_290_fails(self):
self.cs.api_version = api_versions.APIVersion('2.89')
s = self.cs.servers.get(1234)
ex = self.assertRaises(
TypeError,
s.update,
hostname='new-hostname')
self.assertIn('hostname', str(ex))

View File

@ -1502,6 +1502,34 @@ class ShellTest(utils.TestCase):
self.assertRaises(SystemExit, self.run_command, self.assertRaises(SystemExit, self.run_command,
cmd, api_version='2.73') cmd, api_version='2.73')
def test_boot_with_hostname(self):
self.run_command(
'boot --flavor 1 --image %s '
'--hostname my-hostname --nic auto '
'some-server' % FAKE_UUID_1,
api_version='2.90')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'networks': 'auto',
'hostname': 'my-hostname',
}},
)
def test_boot_with_hostname_pre_v290(self):
cmd = (
'boot --flavor 1 --image %s --nic auto '
'--hostname my-hostname some-server' % FAKE_UUID_1
)
self.assertRaises(
SystemExit, self.run_command,
cmd, api_version='2.89')
def test_flavor_list(self): def test_flavor_list(self):
out, _ = self.run_command('flavor-list') out, _ = self.run_command('flavor-list')
self.assert_called_anytime('GET', '/flavors/detail') self.assert_called_anytime('GET', '/flavors/detail')
@ -2258,6 +2286,31 @@ class ShellTest(utils.TestCase):
self.assertNotIn('server_groups', out) self.assertNotIn('server_groups', out)
self.assertNotIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out) self.assertNotIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out)
def test_rebuild_with_hostname(self):
self.run_command(
'rebuild sample-server %s --hostname new-hostname' % FAKE_UUID_1,
api_version='2.90')
self.assert_called('GET', '/servers?name=sample-server', pos=0)
self.assert_called('GET', '/servers/1234', pos=1)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called(
'POST', '/servers/1234/action',
{
'rebuild': {
'imageRef': FAKE_UUID_1,
'description': None,
'hostname': 'new-hostname',
},
},
pos=3)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4)
def test_rebuild_with_hostname_pre_v290(self):
self.assertRaises(
SystemExit, self.run_command,
'rebuild sample-server %s --hostname hostname' % FAKE_UUID_1,
api_version='2.89')
def test_start(self): def test_start(self):
self.run_command('start sample-server') self.run_command('start sample-server')
self.assert_called('POST', '/servers/1234/action', {'os-start': None}) self.assert_called('POST', '/servers/1234/action', {'os-start': None})
@ -2424,6 +2477,25 @@ class ShellTest(utils.TestCase):
'update --description new-description sample-server', 'update --description new-description sample-server',
api_version='2.18') api_version='2.18')
def test_update_with_hostname(self):
self.run_command(
'update --hostname new-hostname sample-server',
api_version='2.90')
expected_put_body = {
"server": {
"hostname": "new-hostname"
}
}
self.assert_called('GET', '/servers/1234', pos=-2)
self.assert_called('PUT', '/servers/1234', expected_put_body, pos=-1)
def test_update_with_hostname_pre_v290(self):
self.assertRaises(
SystemExit,
self.run_command,
'update --hostname new-hostname sample-server',
api_version='2.89')
def test_resize(self): def test_resize(self):
self.run_command('resize sample-server 1') self.run_command('resize sample-server 1')
self.assert_called('POST', '/servers/1234/action', self.assert_called('POST', '/servers/1234/action',

View File

@ -67,17 +67,17 @@ class Server(base.Resource):
@api_versions.wraps("2.0", "2.18") @api_versions.wraps("2.0", "2.18")
def update(self, name=None): def update(self, name=None):
""" """
Update the name for this server. Update attributes of this server.
:param name: Update the server's name. :param name: Update the server's name.
:returns: :class:`Server` :returns: :class:`Server`
""" """
return self.manager.update(self, name=name) return self.manager.update(self, name=name)
@api_versions.wraps("2.19") @api_versions.wraps("2.19", "2.89")
def update(self, name=None, description=None): def update(self, name=None, description=None):
""" """
Update the name and the description for this server. Update attributes of this server.
:param name: Update the server's name. :param name: Update the server's name.
:param description: Update the server's description. :param description: Update the server's description.
@ -88,6 +88,23 @@ class Server(base.Resource):
update_kwargs["description"] = description update_kwargs["description"] = description
return self.manager.update(self, **update_kwargs) return self.manager.update(self, **update_kwargs)
@api_versions.wraps("2.90")
def update(self, name=None, description=None, hostname=None):
"""
Update attributes of this server.
:param name: Update the server's name.
:param description: Update the server's description.
:param hostname: Update the server's hostname.
:returns: :class:`Server`
"""
update_kwargs = {"name": name}
if description is not None:
update_kwargs["description"] = description
if hostname is not None:
update_kwargs["hostname"] = hostname
return self.manager.update(self, **update_kwargs)
def get_console_output(self, length=None): def get_console_output(self, length=None):
""" """
Get text console log output from Server. Get text console log output from Server.
@ -704,7 +721,7 @@ class ServerManager(base.BootingManagerWithFind):
config_drive=None, admin_pass=None, disk_config=None, config_drive=None, admin_pass=None, disk_config=None,
access_ip_v4=None, access_ip_v6=None, description=None, access_ip_v4=None, access_ip_v6=None, description=None,
tags=None, trusted_image_certificates=None, tags=None, trusted_image_certificates=None,
host=None, hypervisor_hostname=None, **kwargs): host=None, hypervisor_hostname=None, hostname=None, **kwargs):
""" """
Create (boot) a new server. Create (boot) a new server.
""" """
@ -833,6 +850,9 @@ class ServerManager(base.BootingManagerWithFind):
if hypervisor_hostname: if hypervisor_hostname:
body['server']['hypervisor_hostname'] = hypervisor_hostname body['server']['hypervisor_hostname'] = hypervisor_hostname
if hostname:
body['server']['hostname'] = hostname
return self._create('/servers', body, response_key, return self._create('/servers', body, response_key,
return_raw=return_raw, **kwargs) return_raw=return_raw, **kwargs)
@ -1318,10 +1338,8 @@ class ServerManager(base.BootingManagerWithFind):
config_drive=None, disk_config=None, admin_pass=None, config_drive=None, disk_config=None, admin_pass=None,
access_ip_v4=None, access_ip_v6=None, access_ip_v4=None, access_ip_v6=None,
trusted_image_certificates=None, trusted_image_certificates=None,
host=None, hypervisor_hostname=None, host=None, hypervisor_hostname=None, hostname=None,
**kwargs): **kwargs):
# TODO(anthony): indicate in doc string if param is an extension
# and/or optional
""" """
Create (boot) a new server. Create (boot) a new server.
@ -1390,6 +1408,8 @@ class ServerManager(base.BootingManagerWithFind):
(allowed since microversion 2.74) (allowed since microversion 2.74)
:param hypervisor_hostname: requested hypervisor hostname to create :param hypervisor_hostname: requested hypervisor hostname to create
servers (allowed since microversion 2.74) servers (allowed since microversion 2.74)
:param hostname: requested hostname of server (allowed since
microversion 2.90)
""" """
if not min_count: if not min_count:
min_count = 1 min_count = 1
@ -1453,6 +1473,10 @@ class ServerManager(base.BootingManagerWithFind):
raise exceptions.UnsupportedAttribute( raise exceptions.UnsupportedAttribute(
"hypervisor_hostname", "2.74") "hypervisor_hostname", "2.74")
hostname_microversion = api_versions.APIVersion("2.90")
if hostname and self.api_version < hostname_microversion:
raise exceptions.UnsupportedAttribute("hostname", "2.90")
boot_kwargs = dict( boot_kwargs = dict(
meta=meta, files=files, userdata=userdata, meta=meta, files=files, userdata=userdata,
reservation_id=reservation_id, min_count=min_count, reservation_id=reservation_id, min_count=min_count,
@ -1463,7 +1487,7 @@ class ServerManager(base.BootingManagerWithFind):
access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6,
trusted_image_certificates=trusted_image_certificates, trusted_image_certificates=trusted_image_certificates,
host=host, hypervisor_hostname=hypervisor_hostname, host=host, hypervisor_hostname=hypervisor_hostname,
**kwargs) hostname=hostname, **kwargs)
if block_device_mapping: if block_device_mapping:
boot_kwargs['block_device_mapping'] = block_device_mapping boot_kwargs['block_device_mapping'] = block_device_mapping
@ -1479,10 +1503,11 @@ class ServerManager(base.BootingManagerWithFind):
@api_versions.wraps("2.0", "2.18") @api_versions.wraps("2.0", "2.18")
def update(self, server, name=None): def update(self, server, name=None):
""" """
Update the name for a server. Update attributes of a server.
:param server: The :class:`Server` (or its ID) to update. :param server: The :class:`Server` (or its ID) to update.
:param name: Update the server's name. :param name: Update the server's name.
:returns: :class:`Server`
""" """
if name is None: if name is None:
return return
@ -1495,15 +1520,16 @@ class ServerManager(base.BootingManagerWithFind):
return self._update("/servers/%s" % base.getid(server), body, "server") return self._update("/servers/%s" % base.getid(server), body, "server")
@api_versions.wraps("2.19") @api_versions.wraps("2.19", "2.89")
def update(self, server, name=None, description=None): def update(self, server, name=None, description=None):
""" """
Update the name or the description for a server. Update attributes of a server.
:param server: The :class:`Server` (or its ID) to update. :param server: The :class:`Server` (or its ID) to update.
:param name: Update the server's name. :param name: Update the server's name.
:param description: Update the server's description. If it equals to :param description: Update the server's description. If it equals to
empty string(i.g. ""), the server description will be removed. empty string(i.g. ""), the server description will be removed.
:returns: :class:`Server`
""" """
if name is None and description is None: if name is None and description is None:
return return
@ -1518,6 +1544,36 @@ class ServerManager(base.BootingManagerWithFind):
return self._update("/servers/%s" % base.getid(server), body, "server") return self._update("/servers/%s" % base.getid(server), body, "server")
@api_versions.wraps("2.90")
def update(self, server, name=None, description=None, hostname=None):
"""
Update attributes of 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.
:param hostname: Update the server's hostname as recorded by the
metadata service. Note that a separate utility running on the
guest will be necessary to reflect these changes in the guest
itself.
:returns: :class:`Server`
"""
if name is None and description is None and hostname is None:
return
body = {"server": {}}
if name:
body["server"]["name"] = name
if description == "":
body["server"]["description"] = None
elif description:
body["server"]["description"] = description
if hostname:
body["server"]["hostname"] = hostname
return self._update("/servers/%s" % base.getid(server), body, "server")
def change_password(self, server, password): def change_password(self, server, password):
""" """
Update the password for a server. Update the password for a server.
@ -1548,6 +1604,7 @@ class ServerManager(base.BootingManagerWithFind):
""" """
return self._action('reboot', server, {'type': reboot_type}) return self._action('reboot', server, {'type': reboot_type})
# TODO(stephenfin): Expand out kwargs
def rebuild(self, server, image, password=None, disk_config=None, def rebuild(self, server, image, password=None, disk_config=None,
preserve_ephemeral=False, name=None, meta=None, files=None, preserve_ephemeral=False, name=None, meta=None, files=None,
**kwargs): **kwargs):
@ -1555,9 +1612,9 @@ class ServerManager(base.BootingManagerWithFind):
Rebuild -- shut down and then re-image -- a server. Rebuild -- shut down and then re-image -- a server.
:param server: The :class:`Server` (or its ID) to share onto. :param server: The :class:`Server` (or its ID) to share onto.
:param image: the :class:`Image` (or its ID) to re-image with. :param image: The :class:`Image` (or its ID) to re-image with.
:param password: string to set as password on the rebuilt server. :param password: String to set as password on the rebuilt server.
:param disk_config: partitioning mode to use on the rebuilt server. :param disk_config: Partitioning mode to use on the rebuilt server.
Valid values are 'AUTO' or 'MANUAL' Valid values are 'AUTO' or 'MANUAL'
:param preserve_ephemeral: If True, request that any ephemeral device :param preserve_ephemeral: If True, request that any ephemeral device
be preserved when rebuilding the instance. Defaults to False. be preserved when rebuilding the instance. Defaults to False.
@ -1565,24 +1622,26 @@ class ServerManager(base.BootingManagerWithFind):
:param meta: A dict of arbitrary key/value metadata to store for this :param meta: A dict of arbitrary key/value metadata to store for this
server. Both keys and values must be <=255 characters. server. Both keys and values must be <=255 characters.
:param files: A dict of files to overwrite on the server upon boot. :param files: A dict of files to overwrite on the server upon boot.
Keys are file names (i.e. ``/etc/passwd``) and values Keys are file names (i.e. ``/etc/passwd``) and values are the file
are the file contents (either as a string or as a contents (either as a string or as a file-like object). A maximum
file-like object). A maximum of five entries is allowed, of five entries is allowed, and each file must be 10k or less.
and each file must be 10k or less.
(deprecated starting with microversion 2.57) (deprecated starting with microversion 2.57)
:param description: optional description of the server (allowed since :param description: Optional description of the server. If None is
microversion 2.19) specified, the existing description will be unset.
:param key_name: optional key pair name for rebuild operation; passing (starting from microversion 2.19)
None will unset the key for the server instance :param key_name: Optional key pair name for rebuild operation. If None
is specified, the existing key will be unset.
(starting from microversion 2.54) (starting from microversion 2.54)
:param userdata: optional user data to pass to be exposed by the :param userdata: Optional user data to pass to be exposed by the
metadata server; this can be a file type object as metadata server; this can be a file type object as well or a
well or a string. If None is specified, the existing string. If None is specified, the existing user_data is unset.
user_data is unset.
(starting from microversion 2.57) (starting from microversion 2.57)
:param trusted_image_certificates: A list of trusted certificate IDs :param trusted_image_certificates: A list of trusted certificate IDs
or None to unset/reset the servers trusted image or None to unset/reset the servers trusted image certificates
certificates (allowed since microversion 2.63) (starting from microversion 2.63)
:param hostname: Optional hostname to configure for the instance. If
None is specified, the existing hostname will be unset.
(starting from microversion 2.90)
:returns: :class:`Server` :returns: :class:`Server`
""" """
descr_microversion = api_versions.APIVersion("2.19") descr_microversion = api_versions.APIVersion("2.19")
@ -1612,6 +1671,12 @@ class ServerManager(base.BootingManagerWithFind):
raise exceptions.UnsupportedAttribute("trusted_image_certificates", raise exceptions.UnsupportedAttribute("trusted_image_certificates",
"2.63") "2.63")
if (
'hostname' in kwargs and
self.api_version < api_versions.APIVersion("2.90")
):
raise exceptions.UnsupportedAttribute('hostname', '2.90')
body = {'imageRef': base.getid(image)} body = {'imageRef': base.getid(image)}
if password is not None: if password is not None:
body['adminPass'] = password body['adminPass'] = password
@ -1628,6 +1693,8 @@ class ServerManager(base.BootingManagerWithFind):
if "trusted_image_certificates" in kwargs: if "trusted_image_certificates" in kwargs:
body["trusted_image_certificates"] = kwargs[ body["trusted_image_certificates"] = kwargs[
"trusted_image_certificates"] "trusted_image_certificates"]
if "hostname" in kwargs:
body["hostname"] = kwargs["hostname"]
if meta: if meta:
body['metadata'] = meta body['metadata'] = meta
if files: if files:

View File

@ -532,8 +532,10 @@ def _boot(cs, args):
if include_files: if include_files:
boot_kwargs['files'] = files boot_kwargs['files'] = files
if ('trusted_image_certificates' in args and if (
args.trusted_image_certificates): 'trusted_image_certificates' in args and
args.trusted_image_certificates
):
boot_kwargs['trusted_image_certificates'] = ( boot_kwargs['trusted_image_certificates'] = (
args.trusted_image_certificates) args.trusted_image_certificates)
elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'):
@ -545,6 +547,9 @@ def _boot(cs, args):
"OS_TRUSTED_IMAGE_CERTIFICATE_IDS", "OS_TRUSTED_IMAGE_CERTIFICATE_IDS",
"2.63") "2.63")
if 'hostname' in args and args.hostname:
boot_kwargs['hostname'] = args.hostname
return boot_args, boot_kwargs return boot_args, boot_kwargs
@ -970,6 +975,14 @@ def _boot(cs, args):
help=_('Requested hypervisor hostname to create servers. Admin only by ' help=_('Requested hypervisor hostname to create servers. Admin only by '
'default.'), 'default.'),
start_version="2.74") start_version="2.74")
@utils.arg(
'--hostname',
help=_(
'Hostname for the instance. This sets the hostname stored in the '
'metadata server: a utility such as cloud-init running on the guest '
'is required to propagate these changes to the guest.'
),
start_version='2.90')
def do_boot(cs, args): def do_boot(cs, args):
"""Boot a new server.""" """Boot a new server."""
boot_args, boot_kwargs = _boot(cs, args) boot_args, boot_kwargs = _boot(cs, args)
@ -2031,6 +2044,14 @@ def do_reboot(cs, args):
help=_("Unset trusted_image_certificates in the server. Cannot be " help=_("Unset trusted_image_certificates in the server. Cannot be "
"specified with the '--trusted-image-certificate-id' option."), "specified with the '--trusted-image-certificate-id' option."),
start_version="2.63") start_version="2.63")
@utils.arg(
'--hostname',
help=_(
'New hostname for the instance. This only updates the hostname '
'stored in the metadata server: a utility running on the guest '
'is required to propagate these changes to the guest.'
),
start_version='2.90')
def do_rebuild(cs, args): def do_rebuild(cs, args):
"""Shutdown, re-image, and re-boot a server.""" """Shutdown, re-image, and re-boot a server."""
server = _find_server(cs, args.server) server = _find_server(cs, args.server)
@ -2121,6 +2142,9 @@ def do_rebuild(cs, args):
"OS_TRUSTED_IMAGE_CERTIFICATE_IDS", "OS_TRUSTED_IMAGE_CERTIFICATE_IDS",
"2.63") "2.63")
if 'hostname' in args and args.hostname is not None:
kwargs['hostname'] = args.hostname
server = server.rebuild(image, _password, **kwargs) server = server.rebuild(image, _password, **kwargs)
_print_server(cs, args, server) _print_server(cs, args, server)
@ -2145,6 +2169,14 @@ def do_rebuild(cs, args):
help=_('New description for the server. If it equals to empty string ' help=_('New description for the server. If it equals to empty string '
'(i.g. ""), the server description will be removed.'), '(i.g. ""), the server description will be removed.'),
start_version="2.19") start_version="2.19")
@utils.arg(
'--hostname',
help=_(
'New hostname for the instance. This only updates the hostname '
'stored in the metadata server: a utility running on the guest '
'is required to propagate these changes to the guest.'
),
start_version='2.90')
def do_update(cs, args): def do_update(cs, args):
"""Update the name or the description for a server.""" """Update the name or the description for a server."""
update_kwargs = {} update_kwargs = {}
@ -2152,6 +2184,8 @@ def do_update(cs, args):
update_kwargs["name"] = args.name update_kwargs["name"] = args.name
if "description" in args and args.description is not None: if "description" in args and args.description is not None:
update_kwargs["description"] = args.description update_kwargs["description"] = args.description
if "hostname" in args and args.hostname is not None:
update_kwargs["hostname"] = args.hostname
_find_server(cs, args.server).update(**update_kwargs) _find_server(cs, args.server).update(**update_kwargs)

View File

@ -0,0 +1,13 @@
---
features:
- |
Added support for `microversion 2.90`_. This microversion provides the
ability to manually configure the instance ``hostname`` attribute when
creating a new instance (``nova boot --hostname HOSTNAME ...``), updating
an existing instance (``nova update --hostname HOSTNAME ...``), or
rebuilding an existing instance (``nova rebuild --hostname HOSTNAME``).
This attribute is published via the metadata service and config drive and
can be used by init scripts such as ``cloud-init`` to configure the guest's
hostname.
.. _microversion 2.90: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-90