Microversion 2.78 - show server topology

Add support microversion 2.78 which adds server topology
information in the output of the following new command:

  nova server-topology

Depends-on: https://review.opendev.org/#/c/621476/
Change-Id: I6467d52d2528a37348458baf4842b571a97f3ed2
Implements: blueprint show-server-numa-topology
This commit is contained in:
Yongli He 2019-07-15 16:36:09 +08:00 committed by Matt Riedemann
parent e43596ca5c
commit aae95dcc7a
9 changed files with 153 additions and 1 deletions

View File

@ -472,6 +472,9 @@ nova usage
'--os-compute-api-version' flag to show help '--os-compute-api-version' flag to show help
message for proper version] message for proper version]
``server-topology``
Retrieve NUMA topology of the given server.
``service-delete`` ``service-delete``
Delete the service. Delete the service.
@ -3358,6 +3361,26 @@ version]
``<tags>`` ``<tags>``
Tag(s) to set. Tag(s) to set.
.. _nova_server_topology:
nova server-topology
--------------------
.. code-block:: console
usage: nova server-topology <server>
Retrieve server NUMA topology information. Host specific fields are only
visible to users with the administrative role.
(Supported by API versions '2.78' - '2.latest')
.. versionadded:: 16.0.0
**Positional arguments:**
``<server>``
Name or ID of server.
.. _nova_service-delete: .. _nova_service-delete:
nova service-delete nova service-delete

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.77") API_MAX_VERSION = api_versions.APIVersion("2.78")

View File

@ -371,6 +371,10 @@ class V1(Base):
self.requests_mock.delete(self.url('1234', 'os-interface', 'port-id'), self.requests_mock.delete(self.url('1234', 'os-interface', 'port-id'),
headers=self.json_headers) headers=self.json_headers)
self.requests_mock.get(self.url('1234', 'topology'),
json=v2_fakes.SERVER_TOPOLOGY,
headers=self.json_headers)
# Testing with the following password and key # Testing with the following password and key
# #
# Clear password: FooBar123 # Clear password: FooBar123

View File

@ -58,6 +58,48 @@ FAKE_RESPONSE_HEADERS = {'x-openstack-request-id': FAKE_REQUEST_ID}
FAKE_SERVICE_UUID_1 = '75e9eabc-ed3b-4f11-8bba-add1e7e7e2de' FAKE_SERVICE_UUID_1 = '75e9eabc-ed3b-4f11-8bba-add1e7e7e2de'
FAKE_SERVICE_UUID_2 = '1f140183-c914-4ddf-8757-6df73028aa86' FAKE_SERVICE_UUID_2 = '1f140183-c914-4ddf-8757-6df73028aa86'
SERVER_TOPOLOGY = {
"nodes": [
{
"cpu_pinning": {
"0": 0,
"1": 5
},
"host_node": 0,
"memory_mb": 1024,
"siblings": [
[
0,
1
]
],
"vcpu_set": [
0,
1
]
},
{
"cpu_pinning": {
"2": 1,
"3": 8
},
"host_node": 1,
"memory_mb": 2048,
"siblings": [
[
2,
3
]
],
"vcpu_set": [
2,
3
]
}
],
"pagesize_kb": 4
}
class FakeClient(fakes.FakeClient, client.Client): class FakeClient(fakes.FakeClient, client.Client):
@ -738,6 +780,9 @@ class FakeSessionClient(base_client.SessionClient):
'rules': []}] 'rules': []}]
}) })
def get_servers_1234_topology(self, **kw):
return 200, {}, SERVER_TOPOLOGY
# #
# Server password # Server password
# #

View File

@ -1875,3 +1875,27 @@ class ServersV277Test(ServersV274Test):
s, availability_zone='foo-az') s, availability_zone='foo-az')
self.assertIn("unexpected keyword argument 'availability_zone'", self.assertIn("unexpected keyword argument 'availability_zone'",
six.text_type(ex)) six.text_type(ex))
class ServersV278Test(ServersV273Test):
api_version = "2.78"
def test_get_server_topology(self):
s = self.cs.servers.get(1234)
topology = s.topology()
self.assert_request_id(topology, fakes.FAKE_REQUEST_ID_LIST)
self.assertIsNotNone(topology)
self.assert_called('GET', '/servers/1234/topology')
topology_from_manager = self.cs.servers.topology(1234)
self.assert_request_id(topology, fakes.FAKE_REQUEST_ID_LIST)
self.assertIsNotNone(topology_from_manager)
self.assert_called('GET', '/servers/1234/topology')
self.assertEqual(topology, topology_from_manager)
def test_get_server_topology_pre278(self):
self.cs.api_version = api_versions.APIVersion('2.77')
s = self.cs.servers.get(1234)
self.assertRaises(exceptions.VersionNotFoundForAPIMethod, s.topology)

View File

@ -2463,6 +2463,19 @@ class ShellTest(utils.TestCase):
self.run_command('diagnostics sample-server') self.run_command('diagnostics sample-server')
self.assert_called('GET', '/servers/1234/diagnostics') self.assert_called('GET', '/servers/1234/diagnostics')
def test_server_topology(self):
self.run_command('server-topology 1234', api_version='2.78')
self.assert_called('GET', '/servers/1234/topology')
self.run_command('server-topology sample-server', api_version='2.78')
self.assert_called('GET', '/servers/1234/topology')
def test_server_topology_pre278(self):
exp = self.assertRaises(SystemExit,
self.run_command,
'server-topology 1234',
api_version='2.77')
self.assertIn('2', six.text_type(exp))
def test_refresh_network(self): def test_refresh_network(self):
self.run_command('refresh-network 1234') self.run_command('refresh-network 1234')
self.assert_called('POST', '/os-server-external-events', self.assert_called('POST', '/os-server-external-events',

View File

@ -316,6 +316,11 @@ class Server(base.Resource):
"""Diagnostics -- Retrieve server diagnostics.""" """Diagnostics -- Retrieve server diagnostics."""
return self.manager.diagnostics(self) return self.manager.diagnostics(self)
@api_versions.wraps("2.78")
def topology(self):
"""Retrieve server topology."""
return self.manager.topology(self)
@api_versions.wraps("2.0", "2.55") @api_versions.wraps("2.0", "2.55")
def migrate(self): def migrate(self):
""" """
@ -1286,6 +1291,19 @@ class ServerManager(base.BootingManagerWithFind):
base.getid(server)) base.getid(server))
return base.TupleWithMeta((resp, body), resp) return base.TupleWithMeta((resp, body), resp)
@api_versions.wraps("2.78")
def topology(self, server):
"""
Retrieve server topology.
:param server: The :class:`Server` (or its ID) for which
topology to be returned
:returns: An instance of novaclient.base.DictWithMeta
"""
resp, body = self.api.client.get("/servers/%s/topology" %
base.getid(server))
return base.DictWithMeta(body, resp)
def _validate_create_nics(self, nics): def _validate_create_nics(self, nics):
# nics are required with microversion 2.37+ and can be a string or list # nics are required with microversion 2.37+ and can be a string or list
if self.api_version > api_versions.APIVersion('2.36'): if self.api_version > api_versions.APIVersion('2.36'):

View File

@ -2313,6 +2313,17 @@ def do_diagnostics(cs, args):
utils.print_dict(cs.servers.diagnostics(server)[1], wrap=80) utils.print_dict(cs.servers.diagnostics(server)[1], wrap=80)
@api_versions.wraps("2.78")
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_server_topology(cs, args):
"""Retrieve server topology."""
server = _find_server(cs, args.server)
# This prints a dict with only two properties: nodes and pagesize_kb
# nodes is a list of dicts so it does not print very well, it's just a
# json blob in the output.
utils.print_dict(cs.servers.topology(server), wrap=80)
@utils.arg( @utils.arg(
'server', metavar='<server>', 'server', metavar='<server>',
help=_('Name or ID of a server for which the network cache should ' help=_('Name or ID of a server for which the network cache should '

View File

@ -0,0 +1,14 @@
---
features:
- |
Added support for `microversion 2.78`_ which outputs the server NUMA
topology information in the following command:
* ``nova server-topology``
And associated python API bindings:
* ``novaclient.v2.servers.Server.topology``
* ``novaclient.v2.servers.ServerManager.topology``
.. _microversion 2.78: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id70