Skip to content

Commit 725b7de

Browse files
committed
compute: Only retrieve necessary images
The Glance API allows us to filter by multiple IDs using the 'in:' operator. Take advantage of this to speed up listing of server in larger deployments where image counts in the hundreds (or even thousands) are not uncommon. Unfortunately the Nova API does not support something similar for listing flavors. Boo. Change-Id: I7d3222d0b0b8bf72b4ff3e429bc49e621b569979 Signed-off-by: Stephen Finucane <sfinucan@redhat.com> Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/837613
1 parent dabaec5 commit 725b7de

3 files changed

Lines changed: 51 additions & 12 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,28 +2479,45 @@ def take_action(self, parsed_args):
24792479
data = compute_client.servers.list(
24802480
search_opts=search_opts,
24812481
marker=marker_id,
2482-
limit=parsed_args.limit)
2482+
limit=parsed_args.limit,
2483+
)
24832484

24842485
images = {}
24852486
flavors = {}
24862487
if data and not parsed_args.no_name_lookup:
2488+
# partial responses from down cells will not have an image
2489+
# attribute so we use getattr
2490+
image_ids = {
2491+
s.image['id'] for s in data
2492+
if getattr(s, 'image', None) and s.image.get('id')
2493+
}
2494+
24872495
# create a dict that maps image_id to image object, which is used
24882496
# to display the "Image Name" column. Note that 'image.id' can be
24892497
# empty for BFV instances and 'image' can be missing entirely if
24902498
# there are infra failures
24912499
if parsed_args.name_lookup_one_by_one or image_id:
2492-
for i_id in set(
2493-
s.image['id'] for s in data
2494-
if s.image and s.image.get('id')
2495-
):
2500+
for image_id in image_ids:
24962501
# "Image Name" is not crucial, so we swallow any exceptions
24972502
try:
2498-
images[i_id] = image_client.get_image(i_id)
2503+
images[image_id] = image_client.get_image(image_id)
24992504
except Exception:
25002505
pass
25012506
else:
25022507
try:
2503-
images_list = image_client.images()
2508+
# some deployments can have *loads* of images so we only
2509+
# want to list the ones we care about. It would be better
2510+
# to only retrun the *fields* we care about (name) but
2511+
# glance doesn't support that
2512+
# NOTE(stephenfin): This could result in super long URLs
2513+
# but it seems unlikely to cause issues. Apache supports
2514+
# URL lengths of up to 8190 characters by default, which
2515+
# should allow for more than 220 unique image ID (different
2516+
# servers are likely use the same image ID) in the filter.
2517+
# Who'd need more than that in a single command?
2518+
images_list = image_client.images(
2519+
id=f"in:{','.join(image_ids)}"
2520+
)
25042521
for i in images_list:
25052522
images[i.id] = i
25062523
except Exception:

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4441,8 +4441,8 @@ def test_server_list_no_servers(self):
44414441
columns, data = self.cmd.take_action(parsed_args)
44424442

44434443
self.servers_mock.list.assert_called_with(**self.kwargs)
4444-
self.assertEqual(0, self.images_mock.list.call_count)
4445-
self.assertEqual(0, self.flavors_mock.list.call_count)
4444+
self.images_mock.assert_not_called()
4445+
self.flavors_mock.list.assert_not_called()
44464446
self.assertEqual(self.columns, columns)
44474447
self.assertEqual(self.data, tuple(data))
44484448

@@ -4465,7 +4465,8 @@ def test_server_list_long_option(self):
44654465
getattr(s, 'OS-EXT-AZ:availability_zone'),
44664466
getattr(s, 'OS-EXT-SRV-ATTR:host'),
44674467
s.Metadata,
4468-
) for s in self.servers)
4468+
) for s in self.servers
4469+
)
44694470
arglist = [
44704471
'--long',
44714472
]
@@ -4477,6 +4478,11 @@ def test_server_list_long_option(self):
44774478

44784479
columns, data = self.cmd.take_action(parsed_args)
44794480
self.servers_mock.list.assert_called_with(**self.kwargs)
4481+
image_ids = {s.image['id'] for s in self.servers if s.image}
4482+
self.images_mock.assert_called_once_with(
4483+
id=f'in:{",".join(image_ids)}',
4484+
)
4485+
self.flavors_mock.list.assert_called_once_with(is_public=None)
44804486
self.assertEqual(self.columns_long, columns)
44814487
self.assertEqual(self.data, tuple(data))
44824488

@@ -4526,6 +4532,8 @@ def test_server_list_no_name_lookup_option(self):
45264532
columns, data = self.cmd.take_action(parsed_args)
45274533

45284534
self.servers_mock.list.assert_called_with(**self.kwargs)
4535+
self.images_mock.assert_not_called()
4536+
self.flavors_mock.list.assert_not_called()
45294537
self.assertEqual(self.columns, columns)
45304538
self.assertEqual(self.data, tuple(data))
45314539

@@ -4554,6 +4562,8 @@ def test_server_list_n_option(self):
45544562
columns, data = self.cmd.take_action(parsed_args)
45554563

45564564
self.servers_mock.list.assert_called_with(**self.kwargs)
4565+
self.images_mock.assert_not_called()
4566+
self.flavors_mock.list.assert_not_called()
45574567
self.assertEqual(self.columns, columns)
45584568
self.assertEqual(self.data, tuple(data))
45594569

@@ -4571,8 +4581,8 @@ def test_server_list_name_lookup_one_by_one(self):
45714581
columns, data = self.cmd.take_action(parsed_args)
45724582

45734583
self.servers_mock.list.assert_called_with(**self.kwargs)
4574-
self.assertFalse(self.images_mock.list.call_count)
4575-
self.assertFalse(self.flavors_mock.list.call_count)
4584+
self.images_mock.assert_not_called()
4585+
self.flavors_mock.list.assert_not_called()
45764586
self.get_image_mock.assert_called()
45774587
self.flavors_mock.get.assert_called()
45784588

@@ -4596,6 +4606,8 @@ def test_server_list_with_image(self):
45964606

45974607
self.search_opts['image'] = self.image.id
45984608
self.servers_mock.list.assert_called_with(**self.kwargs)
4609+
self.images_mock.assert_not_called()
4610+
self.flavors_mock.list.assert_called_once()
45994611

46004612
self.assertEqual(self.columns, columns)
46014613
self.assertEqual(self.data, tuple(data))
@@ -4616,6 +4628,8 @@ def test_server_list_with_flavor(self):
46164628

46174629
self.search_opts['flavor'] = self.flavor.id
46184630
self.servers_mock.list.assert_called_with(**self.kwargs)
4631+
self.images_mock.assert_called_once()
4632+
self.flavors_mock.list.assert_not_called()
46194633

46204634
self.assertEqual(self.columns, columns)
46214635
self.assertEqual(self.data, tuple(data))
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
The ``server list`` needs to query the image service API to retrieve
5+
image names as part of the response. This command will now retrieve only
6+
the images that are relevant, i.e. those used by the server included in
7+
the output. This should result in signficantly faster responses when
8+
using a deployment with a large number of public images.

0 commit comments

Comments
 (0)