Use server filter mode for flavors tables

Horizon uses naive filtering for admin flavors, which doesn't work well
with pagination. Nova API supports flavors filtering, so this patch
replaces the existing filtering with server side filtering.

Change-Id: Icdfc414c4f77bb9fda65b21f509b9f1d453ea7df
Signed-off-by: Tatiana Ovchinnikova <t.v.ovtchinnikova@gmail.com>
This commit is contained in:
Tatiana Ovchinnikova
2025-10-31 22:59:06 -05:00
parent ec95f29725
commit 5acdc88861
5 changed files with 55 additions and 32 deletions

View File

@@ -257,7 +257,7 @@ def flavor_get(request, flavor_id, get_extras=False):
@profiler.trace
@memoized.memoized
def flavor_list(request, is_public=True, get_extras=False):
def flavor_list(request, is_public=None, get_extras=False):
"""Get the list of available instance sizes (flavors)."""
flavors = _nova.novaclient(request).flavors.list(is_public=is_public)
if get_extras:
@@ -290,9 +290,9 @@ def update_pagination(entities, page_size, marker, reversed_order=False):
@profiler.trace
@memoized.memoized
def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
paginate=False, sort_key="name", sort_dir="desc",
reversed_order=False):
def flavor_list_paged(request, is_public=None, min_disk=None, min_ram=None,
get_extras=False, marker=None, paginate=False,
sort_key="name", sort_dir="desc", reversed_order=False):
"""Get the list of available instance sizes (flavors)."""
has_more_data = False
has_prev_data = False
@@ -302,6 +302,8 @@ def flavor_list_paged(request, is_public=True, get_extras=False, marker=None,
sort_dir = 'desc' if sort_dir == 'asc' else 'asc'
page_size = utils.get_page_size(request)
flavors = _nova.novaclient(request).flavors.list(is_public=is_public,
min_disk=min_disk,
min_ram=min_ram,
marker=marker,
limit=page_size + 1,
sort_key=sort_key,

View File

@@ -105,14 +105,13 @@ class ModifyAccess(tables.LinkAction):
class FlavorFilterAction(tables.FilterAction):
def filter(self, table, flavors, filter_string):
"""Really naive case-insensitive search."""
q = filter_string.lower()
def comp(flavor):
return q in flavor.name.lower()
return filter(comp, flavors)
name = 'flavors_filter'
filter_type = 'server'
filter_choices = (
('is_public', _('Is Public ='), True),
('min_disk', _('Minimum Root Disk (GB) ='), True),
('min_ram', _('Minimum RAM (MB) ='), True),
)
def get_size(flavor):

View File

@@ -38,8 +38,9 @@ class FlavorsViewTests(test.BaseAdminViewTests):
self.assertCountEqual(res.context['table'].data, self.flavors.list())
self.mock_flavor_list_paged.assert_called_once_with(
test.IsHttpRequest(), None, marker=None, paginate=True,
sort_dir='asc', sort_key='name', reversed_order=False)
test.IsHttpRequest(), is_public=None, min_disk=None, min_ram=None,
marker=None, paginate=True, sort_dir='asc', sort_key='name',
reversed_order=False)
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_get_keys, 4, mock.call())
@@ -76,15 +77,18 @@ class FlavorsViewTests(test.BaseAdminViewTests):
self.flavors.list()[2:4])
self.mock_flavor_list_paged.assert_has_calls([
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=None, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=None, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=flavors_list[2].id, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
@@ -137,19 +141,23 @@ class FlavorsViewTests(test.BaseAdminViewTests):
self.flavors.list()[:2])
self.mock_flavor_list_paged.assert_has_calls([
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=None, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=None, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=flavors_list[2].id, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=flavors_list[2].id, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=True),
@@ -185,11 +193,13 @@ class FlavorsViewTests(test.BaseAdminViewTests):
self.assertContains(res, form_action, count=1)
self.mock_flavor_list_paged.assert_has_calls([
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=None, paginate=True,
sort_dir='asc', sort_key='name',
reversed_order=False),
mock.call(test.IsHttpRequest(), None,
mock.call(test.IsHttpRequest(), is_public=None,
min_disk=None, min_ram=None,
marker=flavors_list[page_size - 1].id,
paginate=True,
sort_dir='asc', sort_key='name',

View File

@@ -38,6 +38,8 @@ class IndexView(tables.DataTableView):
table_class = project_tables.FlavorsTable
page_title = _("Flavors")
FILTERS_MAPPING = {'is_public': {_('yes'): True, _('no'): False}}
def has_prev_data(self, table):
return self._prev
@@ -46,6 +48,11 @@ class IndexView(tables.DataTableView):
def get_data(self):
request = self.request
filters = self.get_filters(filters_map=self.FILTERS_MAPPING)
is_public = filters.get("is_public")
min_disk = filters.get("min_disk")
min_ram = filters.get("min_ram")
prev_marker = request.GET.get(
project_tables.FlavorsTable._meta.prev_pagination_param, None)
@@ -60,11 +67,14 @@ class IndexView(tables.DataTableView):
# Removing the pagination params and adding "is_public=None"
# will return all flavors.
flavors, self._more, self._prev = api.nova.flavor_list_paged(
request, None,
request,
is_public=is_public,
marker=marker,
paginate=True,
sort_dir='asc',
sort_key='name',
min_disk=min_disk,
min_ram=min_ram,
reversed_order=reversed_order)
except Exception:
self._prev = self._more = False

View File

@@ -491,7 +491,7 @@ class ComputeApiTests(test.APIMockTestCase):
api_flavors = api.nova.flavor_list(self.request)
self.assertEqual(len(flavors), len(api_flavors))
novaclient.flavors.list.assert_called_once_with(is_public=True)
novaclient.flavors.list.assert_called_once_with(is_public=None)
@mock.patch.object(api._nova, 'novaclient')
def test_flavor_get_no_extras(self, mock_novaclient):
@@ -514,7 +514,7 @@ class ComputeApiTests(test.APIMockTestCase):
novaclient.flavors.list.return_value = flavors
api_flavors, has_more, has_prev = api.nova.flavor_list_paged(
self.request, True, False, None, paginate=paginate,
self.request, None, None, None, paginate=paginate,
reversed_order=reversed_order)
for flavor in api_flavors:
@@ -523,11 +523,11 @@ class ComputeApiTests(test.APIMockTestCase):
self.assertFalse(has_prev)
if paginate:
novaclient.flavors.list.assert_called_once_with(
is_public=True, marker=None, limit=page_size + 1,
sort_key='name', sort_dir=order)
is_public=None, min_disk=None, min_ram=None, marker=None,
limit=page_size + 1, sort_key='name', sort_dir=order)
else:
novaclient.flavors.list.assert_called_once_with(
is_public=True)
is_public=None)
@override_settings(API_RESULT_PAGE_SIZE=1)
@mock.patch.object(api._nova, 'novaclient')
@@ -541,7 +541,9 @@ class ComputeApiTests(test.APIMockTestCase):
api_flavors, has_more, has_prev = api.nova\
.flavor_list_paged(
self.request,
True,
None,
None,
None,
False,
marker,
paginate=True)
@@ -552,8 +554,8 @@ class ComputeApiTests(test.APIMockTestCase):
self.assertTrue(has_more)
self.assertTrue(has_prev)
novaclient.flavors.list.assert_called_once_with(
is_public=True, marker=marker, limit=page_size + 1,
sort_key='name', sort_dir='desc')
is_public=None, min_disk=None, min_ram=None, marker=marker,
limit=page_size + 1, sort_key='name', sort_dir='desc')
def test_flavor_list_paged_default_order(self):
self._test_flavor_list_paged()