diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 2e2e11d5ff..bb04b0bd64 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -372,8 +372,7 @@ def flavor_list(request, is_public=True, get_extras=False): @profiler.trace -def update_pagination(entities, page_size, marker, sort_dir, sort_key, - reversed_order): +def update_pagination(entities, page_size, marker, reversed_order=False): has_more_data = has_prev_data = False if len(entities) > page_size: has_more_data = True @@ -389,9 +388,7 @@ def update_pagination(entities, page_size, marker, sort_dir, sort_key, # restore the original ordering here if reversed_order: - entities = sorted(entities, key=lambda entity: - (getattr(entity, sort_key) or '').lower(), - reverse=(sort_dir == 'asc')) + entities.reverse() return entities, has_more_data, has_prev_data @@ -415,7 +412,7 @@ def flavor_list_paged(request, is_public=True, get_extras=False, marker=None, sort_key=sort_key, sort_dir=sort_dir) flavors, has_more_data, has_prev_data = update_pagination( - flavors, page_size, marker, sort_dir, sort_key, reversed_order) + flavors, page_size, marker, reversed_order) else: flavors = novaclient(request).flavors.list(is_public=is_public) @@ -546,6 +543,14 @@ def server_create(request, name, image, flavor, key_name, user_data, @profiler.trace def server_delete(request, instance_id): novaclient(request).servers.delete(instance_id) + # Session is available and consistent for the current view + # among Horizon django servers even in load-balancing setup, + # so only the view listing the servers will recognize it as + # own DeleteInstance action performed. Note that dict is passed + # by reference in python. Quote from django's developer manual: + # " You can read it and write to request.session at any point + # in your view. You can edit it multiple times." + request.session['server_deleted'] = instance_id def get_novaclient_with_locked_status(request): @@ -565,33 +570,66 @@ def server_get(request, instance_id): @profiler.trace -def server_list(request, search_opts=None, detailed=True): +def server_list_paged(request, + search_opts=None, + detailed=True, + sort_dir="desc"): + has_more_data = False + has_prev_data = False nova_client = get_novaclient_with_locked_status(request) page_size = utils.get_page_size(request) - paginate = False - if search_opts is None: - search_opts = {} - elif 'paginate' in search_opts: - paginate = search_opts.pop('paginate') - if paginate: - search_opts['limit'] = page_size + 1 + search_opts = {} if search_opts is None else search_opts + marker = search_opts.get('marker', None) - all_tenants = search_opts.get('all_tenants', False) - if all_tenants: - search_opts['all_tenants'] = True - else: + if not search_opts.get('all_tenants', False): search_opts['project_id'] = request.user.tenant_id - servers = [Server(s, request) - for s in nova_client.servers.list(detailed, search_opts)] + if search_opts.pop('paginate', False): + reversed_order = sort_dir is "asc" + LOG.debug("Notify received on deleted server: %r", + ('server_deleted' in request.session)) + deleted = request.session.pop('server_deleted', + None) + view_marker = 'possibly_deleted' if deleted and marker else 'ok' + search_opts['marker'] = deleted if deleted else marker + search_opts['limit'] = page_size + 1 + search_opts['sort_dir'] = sort_dir + servers = [Server(s, request) + for s in nova_client.servers.list(detailed, search_opts)] + if view_marker == 'possibly_deleted': + if len(servers) == 0: + view_marker = 'head_deleted' + search_opts['sort_dir'] = 'desc' + reversed_order = False + servers = [Server(s, request) + for s in nova_client.servers.list(detailed, + search_opts)] + if len(servers) == 0: + view_marker = 'tail_deleted' + search_opts['sort_dir'] = 'asc' + reversed_order = True + servers = [Server(s, request) + for s in nova_client.servers.list(detailed, + search_opts)] + (servers, has_more_data, has_prev_data) = update_pagination( + servers, page_size, marker, reversed_order) + has_prev_data = (False + if view_marker == 'head_deleted' + else has_prev_data) + has_more_data = (False + if view_marker == 'tail_deleted' + else has_more_data) + else: + servers = [Server(s, request) + for s in nova_client.servers.list(detailed, search_opts)] + return (servers, has_more_data, has_prev_data) - has_more_data = False - if paginate and len(servers) > page_size: - servers.pop(-1) - has_more_data = True - elif paginate and len(servers) == getattr(settings, 'API_RESULT_LIMIT', - 1000): - has_more_data = True + +@profiler.trace +def server_list(request, search_opts=None, detailed=True): + (servers, has_more_data, _) = server_list_paged(request, + search_opts, + detailed) return (servers, has_more_data) diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py index d994cd18f0..a67cf4aa16 100644 --- a/openstack_dashboard/dashboards/admin/instances/tests.py +++ b/openstack_dashboard/dashboards/admin/instances/tests.py @@ -13,10 +13,13 @@ # under the License. from collections import OrderedDict + import uuid import mock +from django.conf import settings +from django.test import override_settings from django.urls import reverse from openstack_dashboard import api @@ -29,7 +32,7 @@ INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' class InstanceViewTest(test.BaseAdminViewTests): @test.create_mocks({ - api.nova: ['flavor_list', 'server_list', 'extension_supported'], + api.nova: ['flavor_list', 'server_list_paged', 'extension_supported'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], }) @@ -42,7 +45,7 @@ class InstanceViewTest(test.BaseAdminViewTests): self.mock_tenant_list.return_value = [self.tenants.list(), False] self.mock_image_list_detailed_by_ids.return_value = self.images.list() self.mock_flavor_list.return_value = self.flavors.list() - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, INDEX_TEMPLATE) @@ -59,11 +62,13 @@ class InstanceViewTest(test.BaseAdminViewTests): test.IsHttpRequest(), instances_img_ids) self.mock_flavor_list.assert_called_once_with(test.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) @test.create_mocks({ - api.nova: ['flavor_list', 'flavor_get', 'server_list', + api.nova: ['flavor_list', 'flavor_get', 'server_list_paged', 'extension_supported'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], @@ -74,7 +79,7 @@ class InstanceViewTest(test.BaseAdminViewTests): instances_img_ids = [instance.image.get('id') for instance in servers if hasattr(instance, 'image')] full_flavors = OrderedDict([(f.id, f) for f in flavors]) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_extension_supported.return_value = True self.mock_flavor_list.side_effect = self.exceptions.nova self.mock_tenant_list.return_value = [self.tenants.list(), False] @@ -92,8 +97,10 @@ class InstanceViewTest(test.BaseAdminViewTests): self.assertItemsEqual(instances, servers) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_extension_supported.assert_has_calls([ mock.call('AdminActions', test.IsHttpRequest()), mock.call('AdminActions', test.IsHttpRequest()), @@ -108,7 +115,7 @@ class InstanceViewTest(test.BaseAdminViewTests): test.IsHttpRequest(), instances_img_ids) @test.create_mocks({ - api.nova: ['flavor_list', 'flavor_get', 'server_list', + api.nova: ['flavor_list', 'flavor_get', 'server_list_paged', 'extension_supported'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], @@ -124,7 +131,7 @@ class InstanceViewTest(test.BaseAdminViewTests): self.mock_image_list_detailed_by_ids.return_value = self.images.list() self.mock_flavor_list.return_value = self.flavors.list() - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_extension_supported.return_value = True self.mock_tenant_list.return_value = [self.tenants.list(), False] self.mock_flavor_get.side_effect = self.exceptions.nova @@ -142,8 +149,10 @@ class InstanceViewTest(test.BaseAdminViewTests): test.IsHttpRequest(), instances_img_ids) self.mock_flavor_list.assert_called_once_with(test.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_extension_supported.assert_has_calls([ mock.call('AdminActions', test.IsHttpRequest()), mock.call('AdminActions', test.IsHttpRequest()), @@ -155,12 +164,12 @@ class InstanceViewTest(test.BaseAdminViewTests): self.assertEqual(len(servers), self.mock_flavor_get.call_count) @test.create_mocks({ - api.nova: ['server_list', 'flavor_list'], + api.nova: ['server_list_paged', 'flavor_list'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], }) def test_index_server_list_exception(self): - self.mock_server_list.side_effect = self.exceptions.nova + self.mock_server_list_paged.side_effect = self.exceptions.nova self.mock_flavor_list.return_value = self.flavors.list() self.mock_tenant_list.return_value = [self.tenants.list(), False] self.mock_image_list_detailed_by_ids.return_value = self.images.list() @@ -170,8 +179,9 @@ class InstanceViewTest(test.BaseAdminViewTests): self.assertEqual(len(res.context['instances_table'].data), 0) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( + self.mock_server_list_paged.assert_called_once_with( test.IsHttpRequest(), + sort_dir='desc', search_opts=search_opts) self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest()) self.mock_image_list_detailed_by_ids.assert_called_once_with( @@ -223,7 +233,7 @@ class InstanceViewTest(test.BaseAdminViewTests): test.IsHttpRequest(), [server]) @test.create_mocks({ - api.nova: ['flavor_list', 'server_list', 'extension_supported'], + api.nova: ['flavor_list', 'server_list_paged', 'extension_supported'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], }) @@ -234,9 +244,9 @@ class InstanceViewTest(test.BaseAdminViewTests): self.mock_tenant_list.return_value = [self.tenants.list(), False] self.mock_image_list_detailed_by_ids.return_value = self.images.list() self.mock_flavor_list.return_value = self.flavors.list() - self.mock_server_list.return_value = [self.servers.list(), False] + self.mock_server_list_paged.return_value = [ + self.servers.list(), False, False] self.mock_extension_supported.return_value = True - res = self.client.get(INDEX_URL) self.assertContains(res, "instances__migrate") self.assertNotContains(res, "instances__confirm") @@ -247,8 +257,10 @@ class InstanceViewTest(test.BaseAdminViewTests): test.IsHttpRequest(), instances_img_ids) self.mock_flavor_list.assert_called_once_with(test.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_extension_supported.assert_has_calls([ mock.call('AdminActions', test.IsHttpRequest()), mock.call('AdminActions', test.IsHttpRequest()), @@ -256,7 +268,7 @@ class InstanceViewTest(test.BaseAdminViewTests): self.assertEqual(12, self.mock_extension_supported.call_count) @test.create_mocks({ - api.nova: ['flavor_list', 'server_list', 'extension_supported'], + api.nova: ['flavor_list', 'server_list_paged', 'extension_supported'], api.keystone: ['tenant_list'], api.glance: ['image_list_detailed_by_ids'], }) @@ -272,7 +284,7 @@ class InstanceViewTest(test.BaseAdminViewTests): self.mock_image_list_detailed_by_ids.return_value = self.images.list() self.mock_flavor_list.return_value = self.flavors.list() self.mock_extension_supported.return_value = True - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] res = self.client.get(INDEX_URL) self.assertContains(res, "instances__confirm") @@ -289,8 +301,10 @@ class InstanceViewTest(test.BaseAdminViewTests): mock.call('Shelve', test.IsHttpRequest())] * 4) self.assertEqual(12, self.mock_extension_supported.call_count) search_opts = {'marker': None, 'paginate': True, 'all_tenants': True} - self.mock_server_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) @test.create_mocks({api.nova: ['service_list', 'server_get']}) @@ -474,3 +488,125 @@ class InstanceViewTest(test.BaseAdminViewTests): self.assertTemplateUsed(res, INDEX_TEMPLATE) instances = res.context['table'].data self.assertItemsEqual(instances, []) + + @test.create_mocks({ + api.nova: ['flavor_list', + 'flavor_get', + 'server_list_paged', + 'extension_supported'], + api.keystone: ['tenant_list'], + api.glance: ['image_list_detailed_by_ids'], + }) + def _test_servers_paginate_do(self, + marker, + servers, + has_more, + has_prev): + flavors = self.flavors.list() + tenants = self.tenants.list() + images = self.images.list() + # UUID indices are unique and are guaranteed being deterministic. + for i, server in enumerate(servers): + server.flavor['id'] = str(uuid.UUID(int=i)) + + self.mock_server_list_paged.return_value = [ + servers, has_more, has_prev] + self.mock_extension_supported.return_value = True + self.mock_flavor_list.return_value = flavors + self.mock_image_list_detailed_by_ids.return_value = images + self.mock_tenant_list.return_value = [tenants, False] + self.mock_flavor_get.side_effect = self.exceptions.nova + + if marker: + url = "?".join([INDEX_URL, "marker={}".format(marker)]) + else: + url = INDEX_URL + res = self.client.get(url) + self.assertTemplateUsed(res, INDEX_TEMPLATE) + self.assertEqual(res.status_code, 200) + + self.mock_extension_supported.assert_has_calls([ + mock.call('AdminActions', test.IsHttpRequest()), + mock.call('AdminActions', test.IsHttpRequest()), + mock.call('Shelve', test.IsHttpRequest())]) + self.assertEqual(3, self.mock_extension_supported.call_count) + self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest()) + self.mock_image_list_detailed_by_ids.assert_called_once_with( + test.IsHttpRequest(), + [server.image.id for server in servers]) + self.mock_flavor_list.assert_called_once_with(test.IsHttpRequest()) + search_opts = {'marker': marker, 'paginate': True, 'all_tenants': True} + self.mock_server_list_paged.assert_called_once_with( + test.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) + self.mock_flavor_get.assert_has_calls( + [mock.call(test.IsHttpRequest(), s.flavor['id']) for s in servers]) + self.assertEqual(len(servers), self.mock_flavor_get.call_count) + + return res + + @override_settings(API_RESULT_PAGE_SIZE=1) + def test_severs_index_paginated(self): + size = settings.API_RESULT_PAGE_SIZE + mox_servers = self.servers.list() + + # get first page + expected_servers = mox_servers[:size] + res = self._test_servers_paginate_do( + marker=None, + servers=expected_servers, + has_more=True, + has_prev=False) + servers = res.context['table'].data + self.assertItemsEqual(servers, expected_servers) + + # get second page + expected_servers = mox_servers[size:2 * size] + marker = expected_servers[0].id + res = self._test_servers_paginate_do( + marker=marker, + servers=expected_servers, + has_more=True, + has_prev=True) + servers = res.context['table'].data + self.assertItemsEqual(servers, expected_servers) + + # get last page + expected_servers = mox_servers[-size:] + marker = expected_servers[0].id + res = self._test_servers_paginate_do( + marker=marker, + servers=expected_servers, + has_more=False, + has_prev=True) + servers = res.context['table'].data + self.assertItemsEqual(servers, expected_servers) + + @override_settings(API_RESULT_PAGE_SIZE=1) + def test_servers_index_paginated_prev(self): + size = settings.API_RESULT_PAGE_SIZE + mox_servers = self.servers.list() + + # prev from some page + expected_servers = mox_servers[size:2 * size] + marker = mox_servers[0].id + + res = self._test_servers_paginate_do( + marker=marker, + servers=expected_servers, + has_more=False, + has_prev=True) + servers = res.context['table'].data + self.assertItemsEqual(servers, expected_servers) + + # back to first page + expected_servers = mox_servers[:size] + marker = mox_servers[0].id + res = self._test_servers_paginate_do( + marker=marker, + servers=expected_servers, + has_more=True, + has_prev=False) + servers = res.context['table'].data + self.assertItemsEqual(servers, expected_servers) diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py index 63454d89cf..0bba73fb1e 100644 --- a/openstack_dashboard/dashboards/admin/instances/views.py +++ b/openstack_dashboard/dashboards/admin/instances/views.py @@ -70,10 +70,13 @@ class AdminUpdateView(views.UpdateView): success_url = reverse_lazy("horizon:admin:instances:index") -class AdminIndexView(tables.DataTableView): +class AdminIndexView(tables.PagedTableMixin, tables.DataTableView): table_class = project_tables.AdminInstancesTable page_title = _("Instances") + def has_prev_data(self, table): + return getattr(self, "_prev", False) + def has_more_data(self, table): return self._more @@ -115,21 +118,21 @@ class AdminIndexView(tables.DataTableView): exceptions.handle(self.request, msg) return {} - def _get_instances(self, search_opts): + def _get_instances(self, search_opts, sort_dir): try: - instances, self._more = api.nova.server_list( + instances, self._more, self._prev = api.nova.server_list_paged( self.request, - search_opts=search_opts) + search_opts=search_opts, + sort_dir=sort_dir) except Exception: - self._more = False + self._more = self._prev = False instances = [] exceptions.handle(self.request, _('Unable to retrieve instance list.')) return instances def get_data(self): - marker = self.request.GET.get( - project_tables.AdminInstancesTable._meta.pagination_param, None) + marker, sort_dir = self._get_marker() default_search_opts = {'marker': marker, 'paginate': True, 'all_tenants': True} @@ -148,7 +151,7 @@ class AdminIndexView(tables.DataTableView): self._needs_filter_first = False - instances = self._get_instances(search_opts) + instances = self._get_instances(search_opts, sort_dir) results = futurist_utils.call_functions_parallel( (self._get_images, [tuple(instances)]), self._get_flavors, diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 2ab3ab13c6..cc9c3032eb 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -170,7 +170,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): @helpers.create_mocks({ api.nova: ( 'flavor_list', - 'server_list', + 'server_list_paged', 'tenant_absolute_limits', 'extension_supported', 'is_feature_available', @@ -192,13 +192,12 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = \ (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_tenant_absolute_limits.return_value = \ self.limits['absolute'] self.mock_floating_ip_supported.return_value = True self.mock_floating_ip_simple_associate_supported.return_value = True - return self.client.get(INDEX_URL) def _check_get_index(self, use_servers_update_address=True, @@ -217,8 +216,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with( - helpers.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) if use_servers_update_address: servers = self.servers.list() self.mock_servers_update_addresses.assert_called_once_with( @@ -261,17 +262,18 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self._check_get_index(use_servers_update_address=False) @helpers.create_mocks({ - api.nova: ('server_list', 'tenant_absolute_limits', 'flavor_list'), + api.nova: ('server_list_paged', + 'tenant_absolute_limits', + 'flavor_list'), api.glance: ('image_list_detailed',), }) def test_index_server_list_exception(self): search_opts = {'marker': None, 'paginate': True} flavors = self.flavors.list() images = self.images.list() - self.mock_flavor_list.return_value = flavors self.mock_image_list_detailed.return_value = (images, False, False) - self.mock_server_list.side_effect = self.exceptions.nova + self.mock_server_list_paged.side_effect = self.exceptions.nova self.mock_tenant_absolute_limits.return_value = self.limits['absolute'] res = self.client.get(INDEX_URL) @@ -283,15 +285,20 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.assert_mock_multiple_calls_with_same_arguments( self.mock_tenant_absolute_limits, 2, mock.call(helpers.IsHttpRequest(), reserved=True)) @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'flavor_get', - 'tenant_absolute_limits', 'extension_supported', + api.nova: ('flavor_list', + 'server_list_paged', + 'flavor_get', + 'tenant_absolute_limits', + 'extension_supported', 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', @@ -305,7 +312,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self._mock_extension_supported({'AdminActions': True, 'Shelve': True}) self.mock_is_feature_available.return_value = True - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.side_effect = self.exceptions.nova self.mock_image_list_detailed.return_value = (self.images.list(), @@ -326,8 +333,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.assert_mock_multiple_calls_with_same_arguments( self.mock_is_feature_available, 8, mock.call(helpers.IsHttpRequest(), 'locked_attribute')) - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) @@ -344,8 +353,11 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): mock.call(helpers.IsHttpRequest())) @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -361,7 +373,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self._mock_extension_supported({'AdminActions': True, 'Shelve': True}) self.mock_is_feature_available.return_value = True - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), @@ -386,8 +398,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.assert_mock_multiple_calls_with_same_arguments( @@ -427,7 +441,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.assertNotIsInstance(action, tables.ConsoleLink) self._check_get_index(multiplier=8) - @helpers.create_mocks({api.nova: ('server_list', + @helpers.create_mocks({api.nova: ('server_list_paged', 'flavor_list', 'server_delete',), api.glance: ('image_list_detailed',), @@ -436,21 +450,22 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): servers = self.servers.list() server = servers[0] - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) self.mock_server_delete.return_value = None - formData = {'action': 'instances__delete__%s' % server.id} res = self.client.post(INDEX_URL, formData) self.assertRedirectsNoFollow(res, INDEX_URL) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) @@ -459,7 +474,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_server_delete.assert_called_once_with( helpers.IsHttpRequest(), server.id) - @helpers.create_mocks({api.nova: ('server_list', + @helpers.create_mocks({api.nova: ('server_list_paged', 'flavor_list', 'server_delete',), api.glance: ('image_list_detailed',), @@ -469,7 +484,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): server = servers[0] server.status = 'ERROR' - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), @@ -482,8 +497,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.assertRedirectsNoFollow(res, INDEX_URL) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) @@ -492,7 +509,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_server_delete.assert_called_once_with( helpers.IsHttpRequest(), server.id) - @helpers.create_mocks({api.nova: ('server_list', + @helpers.create_mocks({api.nova: ('server_list_paged', 'flavor_list', 'server_delete',), api.glance: ('image_list_detailed',), @@ -501,7 +518,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): servers = self.servers.list() server = servers[0] - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), @@ -514,8 +531,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.assertRedirectsNoFollow(res, INDEX_URL) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) @@ -525,7 +544,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_pause', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -539,7 +558,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_pause.return_value = None @@ -554,15 +573,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_pause.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_pause', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -576,7 +597,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_pause.side_effect = self.exceptions.nova @@ -591,15 +612,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_pause.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unpause', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -613,7 +636,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unpause.return_value = None @@ -628,15 +651,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unpause.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unpause', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -651,7 +676,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unpause.side_effect = self.exceptions.nova @@ -666,15 +691,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unpause.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_reboot', - 'server_list', + 'server_list_paged', 'flavor_list',), api.glance: ('image_list_detailed',), api.network: ('servers_update_addresses',)}) @@ -685,7 +712,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_reboot.return_value = None @@ -698,15 +725,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_reboot.assert_called_once_with( helpers.IsHttpRequest(), server.id, soft_reboot=False) @helpers.create_mocks({api.nova: ('server_reboot', - 'server_list', + 'server_list_paged', 'flavor_list',), api.glance: ('image_list_detailed',), api.network: ('servers_update_addresses',)}) @@ -717,7 +746,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_reboot.side_effect = self.exceptions.nova @@ -730,15 +759,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_reboot.assert_called_once_with( helpers.IsHttpRequest(), server.id, soft_reboot=False) @helpers.create_mocks({api.nova: ('server_reboot', - 'server_list', + 'server_list_paged', 'flavor_list',), api.glance: ('image_list_detailed',), api.network: ('servers_update_addresses',)}) @@ -749,7 +780,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_reboot.return_value = None @@ -762,15 +793,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_reboot.assert_called_once_with( helpers.IsHttpRequest(), server.id, soft_reboot=True) @helpers.create_mocks({api.nova: ('server_suspend', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -784,7 +817,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_suspend.return_value = None @@ -797,12 +830,15 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_extension_supported.assert_called_once_with( 'AdminActions', helpers.IsHttpRequest()) - self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + self.mock_flavor_list.assert_called_once_with( + helpers.IsHttpRequest()) self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_suspend.assert_called_once_with( @@ -810,7 +846,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): @django.test.utils.override_settings(API_RESULT_PAGE_SIZE=2) @helpers.create_mocks({api.nova: ('server_suspend', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -824,7 +860,8 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers[page_size:], False] + self.mock_server_list_paged.return_value = [ + servers[page_size:], False, True] self.mock_servers_update_addresses.return_value = None self.mock_server_suspend.return_value = None @@ -842,8 +879,9 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) - self.mock_server_list.assert_called_once_with( + self.mock_server_list_paged.assert_called_once_with( helpers.IsHttpRequest(), + sort_dir='desc', search_opts={'marker': servers[page_size - 1].id, 'paginate': True}) self.mock_servers_update_addresses.assert_called_once_with( @@ -852,7 +890,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), servers[-1].id) @helpers.create_mocks({api.nova: ('server_suspend', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -866,7 +904,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_suspend.side_effect = self.exceptions.nova @@ -881,15 +919,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_suspend.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_resume', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -904,7 +944,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_resume.return_value = None @@ -919,15 +959,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_resume.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_resume', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available'), @@ -942,7 +984,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_resume.side_effect = self.exceptions.nova @@ -957,15 +999,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_resume.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_shelve', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -979,10 +1023,9 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_shelve.return_value = None - formData = {'action': 'instances__shelve__%s' % server.id} res = self.client.post(INDEX_URL, formData) @@ -994,15 +1037,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_shelve.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_shelve', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -1016,7 +1061,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_shelve.side_effect = self.exceptions.nova @@ -1031,15 +1076,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_shelve.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unshelve', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -1054,7 +1101,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unshelve.return_value = None @@ -1069,15 +1116,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unshelve.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unshelve', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -1092,7 +1141,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unshelve.side_effect = self.exceptions.nova @@ -1107,15 +1156,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unshelve.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_lock', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -1130,10 +1181,9 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_lock.return_value = None - formData = {'action': 'instances__lock__%s' % server.id} res = self.client.post(INDEX_URL, formData) @@ -1147,15 +1197,17 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_lock.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_lock', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available',), @@ -1170,7 +1222,7 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_lock.side_effect = self.exceptions.nova @@ -1183,19 +1235,22 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): 'AdminActions', helpers.IsHttpRequest()) self.mock_is_feature_available.assert_called_once_with( helpers.IsHttpRequest(), 'locked_attribute') - self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + self.mock_flavor_list.assert_called_once_with( + helpers.IsHttpRequest()) self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_lock.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unlock', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available'), @@ -1209,10 +1264,9 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unlock.return_value = None - formData = {'action': 'instances__unlock__%s' % server.id} res = self.client.post(INDEX_URL, formData) @@ -1222,19 +1276,22 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): 'AdminActions', helpers.IsHttpRequest()) self.mock_is_feature_available.assert_called_once_with( helpers.IsHttpRequest(), 'locked_attribute') - self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + self.mock_flavor_list.assert_called_once_with( + helpers.IsHttpRequest()) self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unlock.assert_called_once_with( helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_unlock', - 'server_list', + 'server_list_paged', 'flavor_list', 'extension_supported', 'is_feature_available'), @@ -1243,13 +1300,12 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): def test_unlock_instance_exception(self): servers = self.servers.list() server = servers[0] - self.mock_extension_supported.return_value = True self.mock_is_feature_available.return_value = True self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_server_unlock.side_effect = self.exceptions.nova @@ -1266,8 +1322,10 @@ class InstanceTableTests(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.mock_server_unlock.assert_called_once_with( @@ -1743,8 +1801,11 @@ class InstanceTests(InstanceTestBase): self._test_instances_index_retrieve_password_action() @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -1759,7 +1820,7 @@ class InstanceTests(InstanceTestBase): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_tenant_absolute_limits.return_value = self.limits['absolute'] self.mock_floating_ip_supported.return_value = True @@ -1787,8 +1848,10 @@ class InstanceTests(InstanceTestBase): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.assert_mock_multiple_calls_with_same_arguments( @@ -3923,8 +3986,11 @@ class InstanceLaunchInstanceTests(InstanceTestBase, msg, 0) @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -3940,7 +4006,7 @@ class InstanceLaunchInstanceTests(InstanceTestBase, self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_tenant_absolute_limits.return_value = limits self.mock_floating_ip_supported.return_value = True @@ -3967,8 +4033,10 @@ class InstanceLaunchInstanceTests(InstanceTestBase, self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.assert_mock_multiple_calls_with_same_arguments( @@ -3982,8 +4050,11 @@ class InstanceLaunchInstanceTests(InstanceTestBase, mock.call(helpers.IsHttpRequest())) @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -4000,7 +4071,7 @@ class InstanceLaunchInstanceTests(InstanceTestBase, self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_tenant_absolute_limits.return_value = limits self.mock_floating_ip_supported.return_value = True @@ -4026,8 +4097,10 @@ class InstanceLaunchInstanceTests(InstanceTestBase, self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with(helpers.IsHttpRequest(), - search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.assert_mock_multiple_calls_with_same_arguments( @@ -4166,8 +4239,11 @@ class InstanceLaunchInstanceTests(InstanceTestBase, class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -4184,7 +4260,7 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.return_value = [servers, False] + self.mock_server_list_paged.return_value = [servers, False, False] self.mock_servers_update_addresses.return_value = None self.mock_tenant_absolute_limits.return_value = self.limits['absolute'] self.mock_floating_ip_supported.return_value = True @@ -4203,8 +4279,10 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.assert_called_once_with( helpers.IsHttpRequest()) search_opts = {'marker': None, 'paginate': True} - self.mock_server_list.assert_called_once_with( - helpers.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers) self.assert_mock_multiple_calls_with_same_arguments( @@ -4737,8 +4815,11 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): @django.test.utils.override_settings(API_RESULT_PAGE_SIZE=2) @helpers.create_mocks({ - api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', - 'extension_supported', 'is_feature_available',), + api.nova: ('flavor_list', + 'server_list_paged', + 'tenant_absolute_limits', + 'extension_supported', + 'is_feature_available',), api.glance: ('image_list_detailed',), api.neutron: ('floating_ip_simple_associate_supported', 'floating_ip_supported',), @@ -4758,9 +4839,9 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed.return_value = (self.images.list(), False, False) - self.mock_server_list.side_effect = [ - [servers[:page_size], True], - [servers[page_size:], False] + self.mock_server_list_paged.side_effect = [ + [servers[:page_size], True, False], + [servers[page_size:], False, False] ] self.mock_servers_update_addresses.return_value = None @@ -4798,14 +4879,16 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_image_list_detailed, 2, mock.call(helpers.IsHttpRequest())) - self.mock_server_list.assert_has_calls([ + self.mock_server_list_paged.assert_has_calls([ mock.call(helpers.IsHttpRequest(), + sort_dir='desc', search_opts={'marker': None, 'paginate': True}), mock.call(helpers.IsHttpRequest(), + sort_dir='desc', search_opts={'marker': servers[page_size - 1].id, 'paginate': True}), ]) - self.assertEqual(2, self.mock_server_list.call_count) + self.assertEqual(2, self.mock_server_list_paged.call_count) self.mock_servers_update_addresses.assert_has_calls([ mock.call(helpers.IsHttpRequest(), servers[:page_size]), mock.call(helpers.IsHttpRequest(), servers[page_size:]), @@ -4823,7 +4906,7 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): mock.call(helpers.IsHttpRequest())) @django.test.utils.override_settings(API_RESULT_PAGE_SIZE=2) - @helpers.create_mocks({api.nova: ('server_list', + @helpers.create_mocks({api.nova: ('server_list_paged', 'flavor_list', 'server_delete',), api.glance: ('image_list_detailed',), @@ -4835,7 +4918,8 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): servers = self.servers.list()[:3] server = servers[-1] - self.mock_server_list.return_value = [servers[page_size:], False] + self.mock_server_list_paged.return_value = [ + servers[page_size:], False, True] self.mock_servers_update_addresses.return_value = None self.mock_flavor_list.return_value = self.flavors.list() self.mock_image_list_detailed.return_value = (self.images.list(), @@ -4854,8 +4938,10 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.assertMessageCount(success=1) search_opts = {'marker': servers[page_size - 1].id, 'paginate': True} - self.mock_server_list.assert_called_once_with( - helpers.IsHttpRequest(), search_opts=search_opts) + self.mock_server_list_paged.assert_called_once_with( + helpers.IsHttpRequest(), + sort_dir='desc', + search_opts=search_opts) self.mock_servers_update_addresses.assert_called_once_with( helpers.IsHttpRequest(), servers[page_size:]) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py index 9d39f43c62..8ee4c2bb71 100644 --- a/openstack_dashboard/dashboards/project/instances/views.py +++ b/openstack_dashboard/dashboards/project/instances/views.py @@ -59,10 +59,13 @@ from openstack_dashboard.views import get_url_with_pagination LOG = logging.getLogger(__name__) -class IndexView(tables.DataTableView): +class IndexView(tables.PagedTableMixin, tables.DataTableView): table_class = project_tables.InstancesTable page_title = _("Instances") + def has_prev_data(self, table): + return getattr(self, "_prev", False) + def has_more_data(self, table): return self._more @@ -85,13 +88,14 @@ class IndexView(tables.DataTableView): exceptions.handle(self.request, ignore=True) return {} - def _get_instances(self, search_opts): + def _get_instances(self, search_opts, sort_dir): try: - instances, self._more = api.nova.server_list( + instances, self._more, self._prev = api.nova.server_list_paged( self.request, - search_opts=search_opts) + search_opts=search_opts, + sort_dir=sort_dir) except Exception: - self._more = False + self._more = self._prev = False instances = [] exceptions.handle(self.request, _('Unable to retrieve instances.')) @@ -122,8 +126,7 @@ class IndexView(tables.DataTableView): return instances def get_data(self): - marker = self.request.GET.get( - project_tables.InstancesTable._meta.pagination_param, None) + marker, sort_dir = self._get_marker() search_opts = self.get_filters({'marker': marker, 'paginate': True}) image_dict, flavor_dict = futurist_utils.call_functions_parallel( @@ -137,7 +140,7 @@ class IndexView(tables.DataTableView): self._more = False return [] - instances = self._get_instances(search_opts) + instances = self._get_instances(search_opts, sort_dir) # Loop through instances to get flavor info. for instance in instances: diff --git a/openstack_dashboard/test/integration_tests/tests/test_instances.py b/openstack_dashboard/test/integration_tests/tests/test_instances.py index ec8521782c..603e525d42 100644 --- a/openstack_dashboard/test/integration_tests/tests/test_instances.py +++ b/openstack_dashboard/test/integration_tests/tests/test_instances.py @@ -73,7 +73,7 @@ class TestInstances(helpers.TestCase): first_page_definition = {'Next': True, 'Prev': False, 'Count': items_per_page, 'Names': [instance_list[1]]} - second_page_definition = {'Next': False, 'Prev': False, + second_page_definition = {'Next': False, 'Prev': True, 'Count': items_per_page, 'Names': [instance_list[0]]} settings_page = self.home_pg.go_to_settings_usersettingspage() diff --git a/openstack_dashboard/test/unit/api/test_nova.py b/openstack_dashboard/test/unit/api/test_nova.py index 59903bbf6c..9adcb3de07 100644 --- a/openstack_dashboard/test/unit/api/test_nova.py +++ b/openstack_dashboard/test/unit/api/test_nova.py @@ -204,7 +204,8 @@ class ComputeApiTests(test.APIMockTestCase): True, {'all_tenants': True, 'marker': None, - 'limit': page_size + 1}) + 'limit': page_size + 1, + 'sort_dir': 'desc'}) @override_settings(API_RESULT_PAGE_SIZE=1) @mock.patch.object(api.nova, 'novaclient') @@ -229,7 +230,8 @@ class ComputeApiTests(test.APIMockTestCase): True, {'all_tenants': True, 'marker': None, - 'limit': page_size + 1}) + 'limit': page_size + 1, + 'sort_dir': 'desc'}) @mock.patch.object(api.nova, 'novaclient') def test_usage_get(self, mock_novaclient):