diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index 1fb287983..f93c31243 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -952,6 +952,8 @@ nova boot [--description ] [--tags ] [--return-reservation-id] [--trusted-image-certificate-id ] + [--host ] + [--hypervisor-hostname ] Boot a new server. @@ -1117,6 +1119,14 @@ quality of service support, microversion ``2.72`` is required. May be specified multiple times to pass multiple trusted image certificate IDs. (Supported by API versions '2.63' - '2.latest') +``--host `` + Requested host to create servers. Admin only by default. + (Supported by API versions '2.74' - '2.latest') + +``--hypervisor-hostname `` + Requested hypervisor hostname to create servers. Admin only by default. + (Supported by API versions '2.74' - '2.latest') + .. _nova_cell-capacities: nova cell-capacities diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 304660c93..fb9a485eb 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.73") +API_MAX_VERSION = api_versions.APIVersion("2.74") diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 5410f1b31..62c8d8d43 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1743,3 +1743,99 @@ class ServersV273Test(ServersV268Test): self.assert_called('GET', '/servers/detail?locked=False') for s in sl: self.assertIsInstance(s, servers.Server) + + +class ServersV274Test(ServersV273Test): + + api_version = "2.74" + + def test_create_server_with_host(self): + self.cs.servers.create( + name="My server", + image=1, + flavor=1, + nics="auto", + host="new-host" + ) + self.assert_called('POST', '/servers', + {'server': { + 'flavorRef': '1', + 'imageRef': '1', + 'max_count': 1, + 'min_count': 1, + 'name': 'My server', + 'networks': 'auto', + 'host': 'new-host' + }} + ) + + def test_create_server_with_hypervisor_hostname(self): + self.cs.servers.create( + name="My server", + image=1, + flavor=1, + nics="auto", + hypervisor_hostname="new-host" + ) + self.assert_called('POST', '/servers', + {'server': { + 'flavorRef': '1', + 'imageRef': '1', + 'max_count': 1, + 'min_count': 1, + 'name': 'My server', + 'networks': 'auto', + 'hypervisor_hostname': 'new-host' + }} + ) + + def test_create_server_with_host_and_hypervisor_hostname(self): + self.cs.servers.create( + name="My server", + image=1, + flavor=1, + nics="auto", + host="new-host", + hypervisor_hostname="new-host" + ) + self.assert_called('POST', '/servers', + {'server': { + 'flavorRef': '1', + 'imageRef': '1', + 'max_count': 1, + 'min_count': 1, + 'name': 'My server', + 'networks': 'auto', + 'host': 'new-host', + 'hypervisor_hostname': 'new-host' + }} + ) + + def test_create_server_with_host_pre_274_fails(self): + self.cs.api_version = api_versions.APIVersion('2.73') + ex = self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.create, + name="My server", image=1, flavor=1, + nics='auto', host="new-host") + self.assertIn("'host' argument is only allowed since microversion " + "2.74", six.text_type(ex)) + + def test_create_server_with_hypervisor_hostname_pre_274_fails(self): + self.cs.api_version = api_versions.APIVersion('2.73') + ex = self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.create, + name="My server", image=1, flavor=1, + nics='auto', hypervisor_hostname="new-host") + self.assertIn("'hypervisor_hostname' argument is only allowed since " + "microversion 2.74", six.text_type(ex)) + + def test_create_server_with_host_and_hypervisor_hostname_pre_274_fails( + self): + self.cs.api_version = api_versions.APIVersion('2.73') + ex = self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.create, + name="My server", image=1, flavor=1, + nics='auto', host="new-host", + hypervisor_hostname="new-host") + self.assertIn("'host' argument is only allowed since microversion " + "2.74", six.text_type(ex)) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 354475dc2..30ee8bc7a 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -1374,6 +1374,83 @@ class ShellTest(utils.TestCase): self.assertIn('Instance %s could not be found.' % FAKE_UUID_1, six.text_type(ex)) + def test_boot_with_host_v274(self): + self.run_command('boot --flavor 1 --image %s ' + '--host new-host --nic auto ' + 'some-server' % FAKE_UUID_1, + api_version='2.74') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'host': 'new-host', + }}, + ) + + def test_boot_with_hypervisor_hostname_v274(self): + self.run_command('boot --flavor 1 --image %s --nic auto ' + '--hypervisor-hostname new-host ' + 'some-server' % FAKE_UUID_1, + api_version='2.74') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'hypervisor_hostname': 'new-host', + }}, + ) + + def test_boot_with_host_and_hypervisor_hostname_v274(self): + self.run_command('boot --flavor 1 --image %s ' + '--host new-host --nic auto ' + '--hypervisor-hostname new-host ' + 'some-server' % FAKE_UUID_1, + api_version='2.74') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'host': 'new-host', + 'hypervisor_hostname': 'new-host', + }}, + ) + + def test_boot_with_host_pre_v274(self): + cmd = ('boot --flavor 1 --image %s --nic auto ' + '--host new-host some-server' + % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.73') + + def test_boot_with_hypervisor_hostname_pre_v274(self): + cmd = ('boot --flavor 1 --image %s --nic auto ' + '--hypervisor-hostname new-host some-server' + % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.73') + + def test_boot_with_host_and_hypervisor_hostname_pre_v274(self): + cmd = ('boot --flavor 1 --image %s --nic auto ' + '--host new-host --hypervisor-hostname new-host some-server' + % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.73') + def test_flavor_list(self): out, _ = self.run_command('flavor-list') self.assert_called_anytime('GET', '/flavors/detail') @@ -4185,6 +4262,7 @@ class ShellTest(utils.TestCase): 70, # There are no version-wrapped shell method changes for this. 71, # There are no version-wrapped shell method changes for this. 72, # There are no version-wrapped shell method changes for this. + 74, # There are no version-wrapped shell method changes for this. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index 3f003c934..f9f176d2c 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -694,7 +694,8 @@ class ServerManager(base.BootingManagerWithFind): 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, description=None, - tags=None, trusted_image_certificates=None, **kwargs): + tags=None, trusted_image_certificates=None, + host=None, hypervisor_hostname=None, **kwargs): """ Create (boot) a new server. """ @@ -817,6 +818,12 @@ class ServerManager(base.BootingManagerWithFind): body['server']['trusted_image_certificates'] = ( trusted_image_certificates) + if host: + body['server']['host'] = host + + if hypervisor_hostname: + body['server']['hypervisor_hostname'] = hypervisor_hostname + return self._create('/servers', body, response_key, return_raw=return_raw, **kwargs) @@ -1267,7 +1274,9 @@ class ServerManager(base.BootingManagerWithFind): nics=None, scheduler_hints=None, config_drive=None, disk_config=None, admin_pass=None, access_ip_v4=None, access_ip_v6=None, - trusted_image_certificates=None, **kwargs): + trusted_image_certificates=None, + host=None, hypervisor_hostname=None, + **kwargs): # TODO(anthony): indicate in doc string if param is an extension # and/or optional """ @@ -1334,6 +1343,10 @@ class ServerManager(base.BootingManagerWithFind): server as tags (allowed since microversion 2.52) :param trusted_image_certificates: A list of trusted certificate IDs (allowed since microversion 2.63) + :param host: requested host to create servers + (allowed since microversion 2.74) + :param hypervisor_hostname: requested hypervisor hostname to create + servers (allowed since microversion 2.74) """ if not min_count: min_count = 1 @@ -1388,6 +1401,15 @@ class ServerManager(base.BootingManagerWithFind): "Block device volume_type is not supported before " "microversion 2.67") + host_microversion = api_versions.APIVersion("2.74") + if host and self.api_version < host_microversion: + raise exceptions.UnsupportedAttribute("host", "2.74") + hypervisor_hostname_microversion = api_versions.APIVersion("2.74") + if (hypervisor_hostname and + self.api_version < hypervisor_hostname_microversion): + raise exceptions.UnsupportedAttribute( + "hypervisor_hostname", "2.74") + boot_kwargs = dict( meta=meta, files=files, userdata=userdata, reservation_id=reservation_id, min_count=min_count, @@ -1396,7 +1418,9 @@ class ServerManager(base.BootingManagerWithFind): scheduler_hints=scheduler_hints, config_drive=config_drive, disk_config=disk_config, admin_pass=admin_pass, access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, - trusted_image_certificates=trusted_image_certificates, **kwargs) + trusted_image_certificates=trusted_image_certificates, + host=host, hypervisor_hostname=hypervisor_hostname, + **kwargs) if block_device_mapping: boot_kwargs['block_device_mapping'] = block_device_mapping diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 6653aa2f6..3ab22032a 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -517,6 +517,12 @@ def _boot(cs, args): if 'tags' in args and args.tags: boot_kwargs["tags"] = args.tags.split(',') + if 'host' in args and args.host: + boot_kwargs["host"] = args.host + + if 'hypervisor_hostname' in args and args.hypervisor_hostname: + boot_kwargs["hypervisor_hostname"] = args.hypervisor_hostname + if include_files: boot_kwargs['files'] = files @@ -942,6 +948,21 @@ def _boot(cs, args): 'May be specified multiple times to pass multiple trusted image ' 'certificate IDs.'), start_version="2.63") +@utils.arg( + '--host', + metavar='', + dest='host', + default=None, + help=_('Requested host to create servers. Admin only by default.'), + start_version="2.74") +@utils.arg( + '--hypervisor-hostname', + metavar='', + dest='hypervisor_hostname', + default=None, + help=_('Requested hypervisor hostname to create servers. Admin only by ' + 'default.'), + start_version="2.74") def do_boot(cs, args): """Boot a new server.""" boot_args, boot_kwargs = _boot(cs, args) diff --git a/releasenotes/notes/microversion-v2_74-43b128fe6b84b630.yaml b/releasenotes/notes/microversion-v2_74-43b128fe6b84b630.yaml new file mode 100644 index 000000000..de98f8664 --- /dev/null +++ b/releasenotes/notes/microversion-v2_74-43b128fe6b84b630.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Support is added for the `2.74 microversion`_ which allows specifying the + ``--host`` and ``--hypervisor-hostname`` options on the ``nova boot`` + command. The ``novaclient.v2.servers.ServerManager.create()`` method now + also supports ``host`` and ``hypervisor_hostname`` parameters. + + .. _2.74 microversion: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id66