From f200799848831a00f350c324bb77c00efa50da1c Mon Sep 17 00:00:00 2001
From: Yongli He <yongli.he@intel.com>
Date: Mon, 9 Sep 2019 13:56:30 +0800
Subject: [PATCH] compute: Add 'server show --topology' option

Add support for compute microversion 2.78 by adding a '--topology'
option to 'openstack server show' command that retrieves server NUMA
information.

Change-Id: Ie22979df2ea9082ca64a4d43b571bd4025684825
---
 doc/source/cli/data/nova.csv                  |  1 +
 openstackclient/compute/v2/server.py          | 46 +++++++++++++----
 .../tests/unit/compute/v2/test_server.py      | 49 +++++++++++++++++++
 ...y-microversion-v2_78-3891fc67f767177e.yaml |  5 ++
 4 files changed, 92 insertions(+), 9 deletions(-)
 create mode 100644 releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml

diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv
index 872d6e09b2..2004007f8d 100644
--- a/doc/source/cli/data/nova.csv
+++ b/doc/source/cli/data/nova.csv
@@ -121,6 +121,7 @@ start,server start,Start the server(s).
 stop,server stop,Stop the server(s).
 suspend,server suspend,Suspend a server.
 trigger-crash-dump,server dump create,Trigger crash dump in an instance.
+topology,openstack server show --topology,Retrieve server NUMA topology.
 unlock,server unlock,Unlock a server.
 unpause,server unpause,Unpause a server.
 unrescue,server unrescue,Restart the server from normal boot disk again.
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index ba0243ef3e..c1716e5be1 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -3383,7 +3383,8 @@ class ShelveServer(command.Command):
 class ShowServer(command.ShowOne):
     _description = _(
         "Show server details. Specify ``--os-compute-api-version 2.47`` "
-        "or higher to see the embedded flavor information for the server.")
+        "or higher to see the embedded flavor information for the server."
+    )
 
     def get_parser(self, prog_name):
         parser = super(ShowServer, self).get_parser(prog_name)
@@ -3392,18 +3393,29 @@ class ShowServer(command.ShowOne):
             metavar='<server>',
             help=_('Server (name or ID)'),
         )
-        parser.add_argument(
+        # TODO(stephenfin): This should be a separate command, not a flag
+        diagnostics_group = parser.add_mutually_exclusive_group()
+        diagnostics_group.add_argument(
             '--diagnostics',
             action='store_true',
             default=False,
             help=_('Display server diagnostics information'),
         )
+        diagnostics_group.add_argument(
+            '--topology',
+            action='store_true',
+            default=False,
+            help=_(
+                'Include topology information in the output '
+                '(supported by --os-compute-api-version 2.78 or above)'
+            ),
+        )
         return parser
 
     def take_action(self, parsed_args):
         compute_client = self.app.client_manager.compute
-        server = utils.find_resource(compute_client.servers,
-                                     parsed_args.server)
+        server = utils.find_resource(
+            compute_client.servers, parsed_args.server)
 
         if parsed_args.diagnostics:
             (resp, data) = server.diagnostics()
@@ -3412,10 +3424,26 @@ class ShowServer(command.ShowOne):
                     "Error retrieving diagnostics data\n"
                 ))
                 return ({}, {})
-        else:
-            data = _prep_server_detail(compute_client,
-                                       self.app.client_manager.image, server,
-                                       refresh=False)
+            return zip(*sorted(data.items()))
+
+        topology = None
+        if parsed_args.topology:
+            if compute_client.api_version < api_versions.APIVersion('2.78'):
+                msg = _(
+                    '--os-compute-api-version 2.78 or greater is required to '
+                    'support the --topology option'
+                )
+                raise exceptions.CommandError(msg)
+
+            topology = server.topology()
+
+        data = _prep_server_detail(
+            compute_client, self.app.client_manager.image, server,
+            refresh=False)
+
+        if topology:
+            data['topology'] = format_columns.DictColumn(topology)
+
         return zip(*sorted(data.items()))
 
 
@@ -3731,7 +3759,7 @@ class UnsetServer(command.Command):
             help=_(
                 'Tag to remove from the server. '
                 'Specify multiple times to remove multiple tags. '
-                '(supported by --os-compute-api-version 2.26 or later'
+                '(supported by --os-compute-api-version 2.26 or above)'
             ),
         )
         return parser
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index efc105e565..8c3bf317e0 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -6285,6 +6285,10 @@ class TestServerShow(TestServer):
 
         self.image = image_fakes.FakeImage.create_one_image()
         self.flavor = compute_fakes.FakeFlavor.create_one_flavor()
+        self.topology = {
+            'nodes': [{'vcpu_set': [0, 1]}, {'vcpu_set': [2, 3]}],
+            'pagesize_kb': None,
+        }
         server_info = {
             'image': {'id': self.image.id},
             'flavor': {'id': self.flavor.id},
@@ -6298,6 +6302,7 @@ class TestServerShow(TestServer):
         resp.status_code = 200
         server_method = {
             'diagnostics': (resp, {'test': 'test'}),
+            'topology': self.topology,
         }
         self.server = compute_fakes.FakeServer.create_one_server(
             attrs=server_info, methods=server_method)
@@ -6348,6 +6353,7 @@ class TestServerShow(TestServer):
         ]
         verifylist = [
             ('diagnostics', False),
+            ('topology', False),
             ('server', self.server.name),
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -6365,6 +6371,7 @@ class TestServerShow(TestServer):
         ]
         verifylist = [
             ('diagnostics', False),
+            ('topology', False),
             ('server', self.server.name),
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -6391,6 +6398,7 @@ class TestServerShow(TestServer):
         ]
         verifylist = [
             ('diagnostics', True),
+            ('topology', False),
             ('server', self.server.name),
         ]
         parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -6400,6 +6408,47 @@ class TestServerShow(TestServer):
         self.assertEqual(('test',), columns)
         self.assertEqual(('test',), data)
 
+    def test_show_topology(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.78')
+
+        arglist = [
+            '--topology',
+            self.server.name,
+        ]
+        verifylist = [
+            ('diagnostics', False),
+            ('topology', True),
+            ('server', self.server.name),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        self.columns += ('topology',)
+        self.data += (format_columns.DictColumn(self.topology),)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.assertCountEqual(self.columns, columns)
+        self.assertCountEqual(self.data, data)
+
+    def test_show_topology_pre_v278(self):
+        self.app.client_manager.compute.api_version = \
+            api_versions.APIVersion('2.77')
+
+        arglist = [
+            '--topology',
+            self.server.name,
+        ]
+        verifylist = [
+            ('diagnostics', False),
+            ('topology', True),
+            ('server', self.server.name),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        self.assertRaises(
+            exceptions.CommandError, self.cmd.take_action, parsed_args)
+
 
 class TestServerStart(TestServer):
 
diff --git a/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml b/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml
new file mode 100644
index 0000000000..0b48ed009c
--- /dev/null
+++ b/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add support for ``openstack server show --topology`` flag, which will
+    include NUMA topology information in the output.