diff options
7 files changed, 495 insertions, 225 deletions
diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 221b03574..0ee17bf7c 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -377,8 +377,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 @@ -394,9 +393,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 @@ -420,7 +417,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) @@ -551,6 +548,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): @@ -570,33 +575,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 - - all_tenants = search_opts.get('all_tenants', False) - if all_tenants: - search_opts['all_tenants'] = True - else: + search_opts = {} if search_opts is None else search_opts + marker = search_opts.get('marker', None) + + 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 d994cd18f..a67cf4aa1 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 7428657b4..488d1c495 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 d2bbcfdad..7f1d68158 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -172,7 +172,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', @@ -194,13 +194,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, @@ -219,8 +218,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( @@ -263,17 +264,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) @@ -285,15 +287,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', @@ -307,7 +314,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(), @@ -328,8 +335,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()) @@ -346,8 +355,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',), @@ -363,7 +375,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(), @@ -388,8 +400,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( @@ -429,7 +443,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',), @@ -438,21 +452,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()) @@ -461,7 +476,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',), @@ -471,7 +486,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(), @@ -484,8 +499,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()) @@ -494,7 +511,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',), @@ -503,7 +520,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(), @@ -516,8 +533,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()) @@ -527,7 +546,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',), @@ -541,7 +560,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 @@ -556,15 +575,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',), @@ -578,7 +599,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 @@ -593,15 +614,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',), @@ -615,7 +638,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 @@ -630,15 +653,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',), @@ -653,7 +678,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 @@ -668,15 +693,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',)}) @@ -687,7 +714,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 @@ -700,15 +727,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',)}) @@ -719,7 +748,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 @@ -732,15 +761,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',)}) @@ -751,7 +782,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 @@ -764,15 +795,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',), @@ -786,7 +819,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 @@ -799,12 +832,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( @@ -812,7 +848,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',), @@ -826,7 +862,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 @@ -844,8 +881,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( @@ -854,7 +892,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',), @@ -868,7 +906,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 @@ -883,15 +921,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',), @@ -906,7 +946,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 @@ -921,15 +961,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'), @@ -944,7 +986,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 @@ -959,15 +1001,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',), @@ -981,10 +1025,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) @@ -996,15 +1039,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',), @@ -1018,7 +1063,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 @@ -1033,15 +1078,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',), @@ -1056,7 +1103,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 @@ -1071,15 +1118,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',), @@ -1094,7 +1143,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 @@ -1109,15 +1158,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',), @@ -1132,10 +1183,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) @@ -1149,15 +1199,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',), @@ -1172,7 +1224,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 @@ -1185,19 +1237,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'), @@ -1211,10 +1266,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) @@ -1224,19 +1278,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'), @@ -1245,13 +1302,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 @@ -1268,8 +1324,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( @@ -1745,8 +1803,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',), @@ -1761,7 +1822,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 @@ -1789,8 +1850,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( @@ -4030,8 +4093,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',), @@ -4047,7 +4113,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 @@ -4074,8 +4140,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( @@ -4089,8 +4157,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',), @@ -4107,7 +4178,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 @@ -4133,8 +4204,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( @@ -4273,8 +4346,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',), @@ -4291,7 +4367,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 @@ -4310,8 +4386,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( @@ -4844,8 +4922,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',), @@ -4865,9 +4946,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 @@ -4905,14 +4986,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:]), @@ -4930,7 +5013,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',), @@ -4942,7 +5025,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(), @@ -4961,8 +5045,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()) @@ -5050,7 +5136,7 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), server.id, image=image.id, password=password) - @helpers.create_mocks({api.nova: ('server_list', + @helpers.create_mocks({api.nova: ('server_list_paged', 'flavor_list', 'server_unrescue',), api.glance: ('image_list_detailed',), @@ -5060,7 +5146,7 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): server = servers[0] server.status = "RESCUE" - 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(), @@ -5073,8 +5159,10 @@ class InstanceTests2(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()) diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py index fa2e833b7..f17da0412 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 ec8521782..603e525d4 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 a0a042c2f..b2097d0c1 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): |