From 2c57f7bfb2dc39afd361b85639df1ac4e1d417f5 Mon Sep 17 00:00:00 2001
From: Boris Pavlovic <boris@pavlovic.me>
Date: Thu, 29 Jun 2017 15:17:33 -0700
Subject: [PATCH] Add server list -n and --no-name-lookup arguments

Remove translation of Image ID and Flavor ID to Image and Flavor names

In large environments amount of images can be very large (thousands)
Which requires ~hundreds of requests to Glance to get all images
(by default client request only 20 images)
As a result listing even few servers is going to take minutes

This patch allows to avoid these queries by not doing translation,
which allows one to get information about servers in seconds.

Change-Id: I4ae00e6324a41c4c79bf5b620179dae99aea5431
---
 doc/source/cli/command-objects/server.rst     |  5 ++
 openstackclient/compute/v2/server.py          | 51 ++++++++++++-------
 .../tests/unit/compute/v2/test_server.py      | 50 ++++++++++++++++++
 3 files changed, 87 insertions(+), 19 deletions(-)

diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst
index cc5806031c..dee2721943 100644
--- a/doc/source/cli/command-objects/server.rst
+++ b/doc/source/cli/command-objects/server.rst
@@ -330,6 +330,7 @@ List servers
         [--all-projects]
         [--project <project> [--project-domain <project-domain>]]
         [--long]
+        [-n | --no-name-lookup]
         [--marker <server>]
         [--limit <num-servers>]
         [--deleted]
@@ -397,6 +398,10 @@ List servers
 
     List additional fields in output
 
+.. option:: --no-name-lookup
+
+    Skips image and flavor names lookup
+
 .. option:: --marker <server>
 
     The last server of the previous page. Display list of servers
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index e8846d1619..cb520d620e 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -921,6 +921,12 @@ class ListServer(command.Lister):
             default=False,
             help=_('List additional fields in output'),
         )
+        parser.add_argument(
+            '-n', '--no-name-lookup',
+            action='store_true',
+            default=False,
+            help=_('Skip flavor and image name lookup.'),
+        )
         parser.add_argument(
             '--marker',
             metavar='<server>',
@@ -1054,6 +1060,17 @@ class ListServer(command.Lister):
                 'OS-EXT-AZ:availability_zone',
                 'OS-EXT-SRV-ATTR:host',
             ]
+        elif parsed_args.no_name_lookup:
+            columns = (
+                'ID',
+                'Name',
+                'Status',
+                'Image ID',
+                'Flavor ID',
+            )
+            column_headers = tuple(columns)
+            mixed_case_fields = []
+
         else:
             columns = (
                 'ID',
@@ -1062,13 +1079,7 @@ class ListServer(command.Lister):
                 'Networks',
                 'Image Name',
             )
-            column_headers = (
-                'ID',
-                'Name',
-                'Status',
-                'Networks',
-                'Image Name',
-            )
+            column_headers = tuple(columns)
             mixed_case_fields = []
 
         marker_id = None
@@ -1084,23 +1095,25 @@ class ListServer(command.Lister):
         # Create a dict that maps image_id to image object.
         # Needed so that we can display the "Image Name" column.
         # "Image Name" is not crucial, so we swallow any exceptions.
-        try:
-            images_list = self.app.client_manager.image.images.list()
-            for i in images_list:
-                images[i.id] = i
-        except Exception:
-            pass
+        if not parsed_args.no_name_lookup:
+            try:
+                images_list = self.app.client_manager.image.images.list()
+                for i in images_list:
+                    images[i.id] = i
+            except Exception:
+                pass
 
         flavors = {}
         # Create a dict that maps flavor_id to flavor object.
         # Needed so that we can display the "Flavor Name" column.
         # "Flavor Name" is not crucial, so we swallow any exceptions.
-        try:
-            flavors_list = compute_client.flavors.list()
-            for i in flavors_list:
-                flavors[i.id] = i
-        except Exception:
-            pass
+        if not parsed_args.no_name_lookup:
+            try:
+                flavors_list = compute_client.flavors.list()
+                for i in flavors_list:
+                    flavors[i.id] = i
+            except Exception:
+                pass
 
         # Populate image_name, image_id, flavor_name and flavor_id attributes
         # of server objects so that we can display those columns.
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 084171ac1c..3e71ce0771 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -395,6 +395,8 @@ class TestServerCreate(TestServer):
 
         self.assertEqual(self.columns, columns)
         self.assertEqual(self.datalist(), data)
+        self.assertFalse(self.images_mock.called)
+        self.assertFalse(self.flavors_mock.called)
 
     def test_server_create_with_options(self):
         arglist = [
@@ -1457,6 +1459,14 @@ class TestServerList(TestServer):
         'Properties',
     )
 
+    columns_no_name_lookup = (
+        'ID',
+        'Name',
+        'Status',
+        'Image ID',
+        'Flavor ID',
+    )
+
     def setUp(self):
         super(TestServerList, self).setUp()
 
@@ -1515,6 +1525,7 @@ class TestServerList(TestServer):
         # Prepare data returned by fake Nova API.
         self.data = []
         self.data_long = []
+        self.data_no_name_lookup = []
 
         Image = collections.namedtuple('Image', 'id name')
         self.images_mock.list.return_value = [
@@ -1553,6 +1564,13 @@ class TestServerList(TestServer):
                 getattr(s, 'OS-EXT-SRV-ATTR:host'),
                 s.Metadata,
             ))
+            self.data_no_name_lookup.append((
+                s.id,
+                s.name,
+                s.status,
+                s.image['id'],
+                s.flavor['id']
+            ))
 
     def test_server_list_no_option(self):
         arglist = []
@@ -1585,6 +1603,38 @@ class TestServerList(TestServer):
         self.assertEqual(self.columns_long, columns)
         self.assertEqual(tuple(self.data_long), tuple(data))
 
+    def test_server_list_no_name_lookup_option(self):
+        arglist = [
+            '--no-name-lookup',
+        ]
+        verifylist = [
+            ('all_projects', False),
+            ('no_name_lookup', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.servers_mock.list.assert_called_with(**self.kwargs)
+        self.assertEqual(self.columns_no_name_lookup, columns)
+        self.assertEqual(tuple(self.data_no_name_lookup), tuple(data))
+
+    def test_server_list_n_option(self):
+        arglist = [
+            '-n',
+        ]
+        verifylist = [
+            ('all_projects', False),
+            ('no_name_lookup', True),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.servers_mock.list.assert_called_with(**self.kwargs)
+        self.assertEqual(self.columns_no_name_lookup, columns)
+        self.assertEqual(tuple(self.data_no_name_lookup), tuple(data))
+
     def test_server_list_with_image(self):
 
         arglist = [