diff --git a/openstack_dashboard/dashboards/project/floating_ips/forms.py b/openstack_dashboard/dashboards/project/floating_ips/forms.py index 6e4daf6ef7..765f45425e 100644 --- a/openstack_dashboard/dashboards/project/floating_ips/forms.py +++ b/openstack_dashboard/dashboards/project/floating_ips/forms.py @@ -38,7 +38,8 @@ class FloatingIpAllocate(forms.SelfHandlingForm): def handle(self, request, data): try: # Prevent allocating more IP than the quota allows - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, + targets=['floating_ips']) if usages['floating_ips']['available'] <= 0: error_message = _('You are already using all of your available' ' floating IPs.') diff --git a/openstack_dashboard/dashboards/project/floating_ips/tables.py b/openstack_dashboard/dashboards/project/floating_ips/tables.py index e02475e7c6..13e3ec2a22 100644 --- a/openstack_dashboard/dashboards/project/floating_ips/tables.py +++ b/openstack_dashboard/dashboards/project/floating_ips/tables.py @@ -47,7 +47,8 @@ class AllocateIP(tables.LinkAction): return shortcuts.redirect('horizon:project:floating_ips:index') def allowed(self, request, fip=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, + targets=['floating_ips']) if usages['floating_ips']['available'] <= 0: if "disabled" not in self.classes: self.classes = [c for c in self.classes] + ['disabled'] diff --git a/openstack_dashboard/dashboards/project/floating_ips/tests.py b/openstack_dashboard/dashboards/project/floating_ips/tests.py index 32125894f3..3db0e069da 100644 --- a/openstack_dashboard/dashboards/project/floating_ips/tests.py +++ b/openstack_dashboard/dashboards/project/floating_ips/tests.py @@ -266,7 +266,7 @@ class FloatingIpViewTests(test.TestCase): IsA(http.HttpRequest), detailed=False) \ .AndReturn([self.servers.list(), False]) quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ + IsA(http.HttpRequest), targets=['floating_ips']).MultipleTimes() \ .AndReturn(quota_data) self.mox.ReplayAll() @@ -304,7 +304,7 @@ class FloatingIpViewTests(test.TestCase): IsA(http.HttpRequest), detailed=False) \ .AndReturn([self.servers.list(), False]) quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ + IsA(http.HttpRequest), targets=['floating_ips']).MultipleTimes() \ .AndReturn(quota_data) self.mox.ReplayAll() @@ -318,38 +318,22 @@ class FloatingIpViewTests(test.TestCase): self.assertEqual('Allocate IP To Project (Quota exceeded)', six.text_type(allocate_action.verbose_name)) - @test.create_stubs({api.nova: ('tenant_quota_get', 'flavor_list', - 'server_list'), - api.neutron: ('floating_ip_pools_list', + @test.create_stubs({api.neutron: ('floating_ip_pools_list', 'floating_ip_supported', - 'security_group_list', 'tenant_floating_ip_list', 'is_extension_supported', 'is_router_enabled', - 'tenant_quota_get', - 'network_list', - 'router_list', - 'subnet_list'), + 'tenant_quota_get'), api.base: ('is_service_enabled',), api.cinder: ('is_volume_service_enabled',)}) @test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True}) def test_correct_quotas_displayed(self): - servers = [s for s in self.servers.list() - if s.tenant_id == self.request.user.tenant_id] - api.cinder.is_volume_service_enabled(IsA(http.HttpRequest)) \ .AndReturn(False) api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ .MultipleTimes().AndReturn(True) api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \ .MultipleTimes().AndReturn(True) - api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \ - .AndReturn(self.quotas.first()) - api.nova.flavor_list(IsA(http.HttpRequest)) \ - .AndReturn(self.flavors.list()) - search_opts = {'tenant_id': self.request.user.tenant_id} - api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \ - .AndReturn([servers, False]) api.neutron.is_extension_supported( IsA(http.HttpRequest), 'security-group').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'quotas') \ @@ -358,23 +342,12 @@ class FloatingIpViewTests(test.TestCase): .AndReturn(True) api.neutron.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \ .AndReturn(self.neutron_quotas.first()) - api.neutron.router_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id) \ - .AndReturn(self.routers.list()) - api.neutron.subnet_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id) \ - .AndReturn(self.subnets.list()) - api.neutron.network_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id) \ - .AndReturn(self.networks.list()) api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \ .AndReturn(True) api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(self.floating_ips.list()) api.neutron.floating_ip_pools_list(IsA(http.HttpRequest)) \ .AndReturn(self.pools.list()) - api.neutron.security_group_list(IsA(http.HttpRequest)) \ - .AndReturn(self.security_groups.list()) self.mox.ReplayAll() url = reverse('%s:allocate' % NAMESPACE) diff --git a/openstack_dashboard/dashboards/project/floating_ips/views.py b/openstack_dashboard/dashboards/project/floating_ips/views.py index dfd2a52cf6..7db8058765 100644 --- a/openstack_dashboard/dashboards/project/floating_ips/views.py +++ b/openstack_dashboard/dashboards/project/floating_ips/views.py @@ -61,7 +61,8 @@ class AllocateView(forms.ModalFormView): def get_context_data(self, **kwargs): context = super(AllocateView, self).get_context_data(**kwargs) try: - context['usages'] = quotas.tenant_quota_usages(self.request) + context['usages'] = quotas.tenant_quota_usages( + self.request, targets=['floating_ips']) except Exception: exceptions.handle(self.request) return context diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 41143113a7..faf8d83c1c 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -1995,7 +1995,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): disk_config=disk_config_value, config_drive=config_drive_value, scheduler_hints=scheduler_hints) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -2123,7 +2125,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): disk_config=u'AUTO', config_drive=True, scheduler_hints={}) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) self.mox.ReplayAll() @@ -2208,7 +2212,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): cinder.volume_snapshot_list(IsA(http.HttpRequest), search_opts=SNAPSHOT_SEARCH_OPTS) \ .AndReturn([]) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.extension_supported('BlockDeviceMappingV2Boot', @@ -2312,7 +2318,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): cinder.volume_snapshot_list(IsA(http.HttpRequest), search_opts=SNAPSHOT_SEARCH_OPTS) \ .AndReturn([]) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) self.mox.ReplayAll() @@ -2426,8 +2434,10 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): disk_config=u'AUTO', config_drive=True, scheduler_hints={}) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ - .AndReturn(quota_usages) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ + .AndReturn(quota_usages) self.mox.ReplayAll() @@ -2496,7 +2506,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): 'status': 'active'}) \ .AndReturn([[], False, False]) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) self._mock_neutron_network_and_port_list() @@ -2664,7 +2676,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): config_drive=False, scheduler_hints={}) \ .AndRaise(self.exceptions.keystone) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -2748,7 +2762,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): .AndReturn(self.flavors.list()) quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ .AndReturn(self.limits['absolute']) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -2835,7 +2851,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): .AndReturn(self.flavors.list()) quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ .AndReturn(self.limits['absolute']) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -2930,7 +2948,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): .AndReturn(self.flavors.list()) quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ .AndReturn(self.limits['absolute']) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -3075,7 +3095,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): quotas.tenant_limit_usages( IsA(http.HttpRequest)).AndReturn(self.limits['absolute']) quotas.tenant_quota_usages( - IsA(http.HttpRequest)).AndReturn(quota_usages) + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ + .AndReturn(quota_usages) api.nova.flavor_list( IsA(http.HttpRequest)).AndReturn(self.flavors.list()) @@ -3202,7 +3224,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): .AndReturn(self.flavors.list()) quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ .AndReturn(self.limits['absolute']) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) @@ -3405,7 +3429,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) - quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['instances', 'cores', 'ram', 'volumes']) \ .AndReturn(quota_usages) api.nova.server_create(IsA(http.HttpRequest), diff --git a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py index bc587c7f42..c4e73c8d07 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py @@ -207,7 +207,9 @@ class SetInstanceDetailsAction(workflows.Action): count = cleaned_data.get('count', 1) # Prevent launching more instances than the quota allows - usages = quotas.tenant_quota_usages(self.request) + usages = quotas.tenant_quota_usages( + self.request, + targets=['instances', 'cores', 'ram', 'volumes']) available_count = usages['instances']['available'] if available_count < count: msg = (_('The requested instance(s) cannot be launched ' diff --git a/openstack_dashboard/dashboards/project/key_pairs/tables.py b/openstack_dashboard/dashboards/project/key_pairs/tables.py index c6fe9366b2..7ce6cd3899 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/tables.py +++ b/openstack_dashboard/dashboards/project/key_pairs/tables.py @@ -50,7 +50,7 @@ class DeleteKeyPairs(tables.DeleteAction): class QuotaKeypairMixin(object): def allowed(self, request, datum=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=['key_pairs']) count = len(self.table.data) if (usages.get('key_pairs') and usages['key_pairs']['quota'] <= count): if "disabled" not in self.classes: diff --git a/openstack_dashboard/dashboards/project/key_pairs/tests.py b/openstack_dashboard/dashboards/project/key_pairs/tests.py index c7f9027886..22d09bee5e 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/tests.py +++ b/openstack_dashboard/dashboards/project/key_pairs/tests.py @@ -42,7 +42,8 @@ class KeyPairTests(test.TestCase): keypairs = self.keypairs.list() quota_data = self.quota_usages.first() - quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \ + quotas.tenant_quota_usages(IsA(http.HttpRequest), + targets=['key_pairs']).MultipleTimes() \ .AndReturn(quota_data) api.nova.keypair_list(IsA(http.HttpRequest)) \ .AndReturn(keypairs) diff --git a/openstack_dashboard/dashboards/project/network_topology/tests.py b/openstack_dashboard/dashboards/project/network_topology/tests.py index 0328219022..78c53ff143 100644 --- a/openstack_dashboard/dashboards/project/network_topology/tests.py +++ b/openstack_dashboard/dashboards/project/network_topology/tests.py @@ -198,8 +198,14 @@ class NetworkTopologyCreateTests(test.TestCase): quota_data['instances']['available'] = instances_quota quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ - .MultipleTimes().AndReturn(quota_data) + IsA(http.HttpRequest), targets=['instances'] + ).MultipleTimes().AndReturn(quota_data) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), targets=['networks'] + ).MultipleTimes().AndReturn(quota_data) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), targets=['routers'] + ).MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() diff --git a/openstack_dashboard/dashboards/project/network_topology/utils.py b/openstack_dashboard/dashboards/project/network_topology/utils.py index b0abec1f1b..561c9026a4 100644 --- a/openstack_dashboard/dashboards/project/network_topology/utils.py +++ b/openstack_dashboard/dashboards/project/network_topology/utils.py @@ -18,7 +18,7 @@ from openstack_dashboard.usage import quotas def _quota_exceeded(request, quota): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=[quota]) available = usages.get(quota, {}).get('available', 1) return available <= 0 diff --git a/openstack_dashboard/dashboards/project/networks/subnets/tables.py b/openstack_dashboard/dashboards/project/networks/subnets/tables.py index ebb9b94a5a..8cb3a467ea 100644 --- a/openstack_dashboard/dashboards/project/networks/subnets/tables.py +++ b/openstack_dashboard/dashboards/project/networks/subnets/tables.py @@ -103,7 +103,7 @@ class CreateSubnet(SubnetPolicyTargetMixin, CheckNetworkEditable, return reverse(self.url, args=(network_id,)) def allowed(self, request, datum=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=['subnets']) # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False # usages["subnets'] is empty diff --git a/openstack_dashboard/dashboards/project/networks/tables.py b/openstack_dashboard/dashboards/project/networks/tables.py index 85140ecbaa..1e09a32d14 100644 --- a/openstack_dashboard/dashboards/project/networks/tables.py +++ b/openstack_dashboard/dashboards/project/networks/tables.py @@ -92,7 +92,7 @@ class CreateNetwork(tables.LinkAction): policy_rules = (("network", "create_network"),) def allowed(self, request, datum=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=['networks']) # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False # usages["networks"] is empty if usages.get('networks', {}).get('available', 1) <= 0: @@ -129,7 +129,7 @@ class CreateSubnet(policy.PolicyTargetMixin, CheckNetworkEditable, ("network:project_id", "tenant_id"),) def allowed(self, request, datum=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=['subnets']) # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False # usages["subnets'] is empty if usages.get('subnets', {}).get('available', 1) <= 0: diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py index bccbd90a8c..7ef1ec4af1 100644 --- a/openstack_dashboard/dashboards/project/networks/tests.py +++ b/openstack_dashboard/dashboards/project/networks/tests.py @@ -132,7 +132,10 @@ class NetworkTests(test.TestCase, NetworkStubMixin): quota_data['subnets']['available'] = 5 self._stub_net_list() quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['networks']) \ + .MultipleTimes().AndReturn(quota_data) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -151,7 +154,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin): tenant_id=self.tenant.id, shared=False).MultipleTimes().AndRaise(self.exceptions.neutron) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['networks']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -190,7 +193,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin): .AndReturn(mac_learning) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -217,7 +220,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'mac-learning')\ .AndReturn(mac_learning) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -288,7 +291,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'mac-learning')\ .AndReturn(mac_learning) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -330,7 +333,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'mac-learning')\ .AndReturn(mac_learning) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -992,7 +995,10 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): self._stub_net_list() quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['networks']) \ + .MultipleTimes().AndReturn(quota_data) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -1018,7 +1024,10 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): self._stub_net_list() quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['networks']) \ + .MultipleTimes().AndReturn(quota_data) + quotas.tenant_quota_usages( + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() @@ -1096,7 +1105,7 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(False) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['subnets']) \ .MultipleTimes().AndReturn(quota_data) self.mox.ReplayAll() diff --git a/openstack_dashboard/dashboards/project/routers/tables.py b/openstack_dashboard/dashboards/project/routers/tables.py index a97c4e864f..ad1bc0accd 100644 --- a/openstack_dashboard/dashboards/project/routers/tables.py +++ b/openstack_dashboard/dashboards/project/routers/tables.py @@ -96,7 +96,7 @@ class CreateRouter(tables.LinkAction): policy_rules = (("network", "create_router"),) def allowed(self, request, datum=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, targets=['routers']) # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False # usages['routers'] is empty if usages.get('routers', {}).get('available', 1) <= 0: diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py index 2f60aed9fa..8ec4181609 100644 --- a/openstack_dashboard/dashboards/project/routers/tests.py +++ b/openstack_dashboard/dashboards/project/routers/tests.py @@ -93,7 +93,7 @@ class RouterTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() self.mox.ReplayAll() @@ -113,7 +113,7 @@ class RouterTests(RouterMixin, test.TestCase): tenant_id=self.tenant.id).MultipleTimes().AndRaise( self.exceptions.neutron) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() self.mox.ReplayAll() @@ -133,7 +133,7 @@ class RouterTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).MultipleTimes().AndReturn([router]) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list(alter_ids=True) self.mox.ReplayAll() @@ -177,7 +177,7 @@ class RouterTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() api.neutron.router_list( @@ -215,7 +215,7 @@ class RouterTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() api.neutron.router_list( @@ -801,7 +801,7 @@ class RouterViewTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() @@ -828,7 +828,7 @@ class RouterViewTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() @@ -855,7 +855,7 @@ class RouterViewTests(RouterMixin, test.TestCase): IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(self.routers.list()) quotas.tenant_quota_usages( - IsA(http.HttpRequest)) \ + IsA(http.HttpRequest), targets=['routers']) \ .MultipleTimes().AndReturn(quota_data) self._mock_external_network_list() diff --git a/openstack_dashboard/dashboards/project/security_groups/tables.py b/openstack_dashboard/dashboards/project/security_groups/tables.py index 2b619d18cf..9479cd0742 100644 --- a/openstack_dashboard/dashboards/project/security_groups/tables.py +++ b/openstack_dashboard/dashboards/project/security_groups/tables.py @@ -63,7 +63,8 @@ class CreateGroup(tables.LinkAction): icon = "plus" def allowed(self, request, security_group=None): - usages = quotas.tenant_quota_usages(request) + usages = quotas.tenant_quota_usages(request, + targets=['security_groups']) if usages['security_groups'].get('available', 1) <= 0: if "disabled" not in self.classes: self.classes = [c for c in self.classes] + ["disabled"] diff --git a/openstack_dashboard/dashboards/project/security_groups/tests.py b/openstack_dashboard/dashboards/project/security_groups/tests.py index 6691f04e44..f54fc1434c 100644 --- a/openstack_dashboard/dashboards/project/security_groups/tests.py +++ b/openstack_dashboard/dashboards/project/security_groups/tests.py @@ -74,7 +74,9 @@ class SecurityGroupsViewTests(test.TestCase): api.neutron.security_group_list(IsA(http.HttpRequest)) \ .AndReturn(sec_groups) - quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \ + quotas.tenant_quota_usages( + IsA(http.HttpRequest), + targets=['security_groups']).MultipleTimes() \ .AndReturn(quota_data) self.mox.ReplayAll() @@ -107,7 +109,8 @@ class SecurityGroupsViewTests(test.TestCase): IsA(http.HttpRequest)) \ .AndReturn(sec_groups) quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ + IsA(http.HttpRequest), + targets=['security_groups']).MultipleTimes() \ .AndReturn(quota_data) self.mox.ReplayAll() @@ -140,7 +143,8 @@ class SecurityGroupsViewTests(test.TestCase): IsA(http.HttpRequest)) \ .AndReturn(sec_groups) quotas.tenant_quota_usages( - IsA(http.HttpRequest)).MultipleTimes() \ + IsA(http.HttpRequest), + targets=['security_groups']).MultipleTimes() \ .AndReturn(quota_data) self.mox.ReplayAll() diff --git a/openstack_dashboard/test/tests/quotas.py b/openstack_dashboard/test/tests/quotas.py index 649fe7a2fe..ec36bf4a8d 100644 --- a/openstack_dashboard/test/tests/quotas.py +++ b/openstack_dashboard/test/tests/quotas.py @@ -412,3 +412,161 @@ class QuotaTests(test.APITestCase): expected = set(['floating_ips', 'fixed_ips', 'security_groups', 'security_group_rules', 'router', 'floatingip']) self.assertEqual(expected, disabled_quotas) + + def test_tenant_quota_usages_with_target_instances(self): + self._test_tenant_quota_usages_with_target(targets=['instances']) + + def test_tenant_quota_usages_with_target_ram(self): + self._test_tenant_quota_usages_with_target( + targets=['ram'], use_flavor_list=True) + + def test_tenant_quota_usages_with_target_volume(self): + self._test_tenant_quota_usages_with_target( + targets=['volumes'], use_compute_call=False, use_cinder_call=True) + + def test_tenant_quota_usages_with_target_compute_volume(self): + self._test_tenant_quota_usages_with_target( + targets=['instances', 'cores', 'ram', 'volumes'], + use_flavor_list=True, use_cinder_call=True) + + @test.create_stubs({api.nova: ('server_list', + 'flavor_list', + 'tenant_quota_get',), + api.base: ('is_service_enabled',), + cinder: ('volume_list', 'volume_snapshot_list', + 'tenant_quota_get', + 'is_volume_service_enabled')}) + def _test_tenant_quota_usages_with_target( + self, targets, + use_compute_call=True, + use_flavor_list=False, use_cinder_call=False): + cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ + .AndReturn(False) + api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \ + .MultipleTimes().AndReturn(True) + + if use_compute_call: + servers = [s for s in self.servers.list() + if s.tenant_id == self.request.user.tenant_id] + if use_flavor_list: + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + search_opts = {'tenant_id': self.request.user.tenant_id} + api.nova.server_list(IsA(http.HttpRequest), + search_opts=search_opts) \ + .AndReturn([servers, False]) + api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \ + .AndReturn(self.quotas.first()) + + if use_cinder_call: + opts = {'all_tenants': 1, + 'project_id': self.request.user.tenant_id} + cinder.volume_list(IsA(http.HttpRequest), opts) \ + .AndReturn(self.volumes.list()) + cinder.volume_snapshot_list(IsA(http.HttpRequest), opts) \ + .AndReturn(self.cinder_volume_snapshots.list()) + cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \ + .AndReturn(self.cinder_quotas.first()) + + self.mox.ReplayAll() + + quota_usages = quotas.tenant_quota_usages(self.request, + targets=targets) + + expected = self.get_usages() + expected = dict((k, v) for k, v in expected.items() if k in targets) + + # Compare internal structure of usages to expected. + self.assertItemsEqual(expected, quota_usages.usages) + # Compare available resources + self.assertAvailableQuotasEqual(expected, quota_usages.usages) + + def test_tenant_quota_usages_neutron_with_target_network_resources(self): + self._test_tenant_quota_usages_neutron_with_target( + targets=['networks', 'subnets', 'routers']) + + def test_tenant_quota_usages_neutron_with_target_security_groups(self): + self._test_tenant_quota_usages_neutron_with_target( + targets=['security_groups']) + + def test_tenant_quota_usages_neutron_with_target_floating_ips(self): + self._test_tenant_quota_usages_neutron_with_target( + targets=['floating_ips']) + + @test.create_stubs({api.base: ('is_service_enabled',), + api.neutron: ('floating_ip_supported', + 'tenant_floating_ip_list', + 'security_group_list', + 'is_extension_supported', + 'is_router_enabled', + 'is_quotas_extension_supported', + 'tenant_quota_get', + 'network_list', + 'subnet_list', + 'router_list'), + cinder: ('is_volume_service_enabled',)}) + def _test_tenant_quota_usages_neutron_with_target( + self, targets): + cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ + .AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'security-group').AndReturn(True) + api.neutron.is_router_enabled(IsA(http.HttpRequest)).AndReturn(True) + api.neutron.is_quotas_extension_supported(IsA(http.HttpRequest)) \ + .AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \ + .MultipleTimes().AndReturn(True) + + api.neutron.tenant_quota_get(IsA(http.HttpRequest), '1') \ + .AndReturn(self.neutron_quotas.first()) + if 'networks' in targets: + api.neutron.network_list(IsA(http.HttpRequest), + tenant_id=self.request.user.tenant_id) \ + .AndReturn(self.networks.list()) + if 'subnets' in targets: + api.neutron.subnet_list(IsA(http.HttpRequest), + tenant_id=self.request.user.tenant_id) \ + .AndReturn(self.subnets.list()) + if 'routers' in targets: + api.neutron.router_list(IsA(http.HttpRequest), + tenant_id=self.request.user.tenant_id) \ + .AndReturn(self.routers.list()) + if 'floating_ips' in targets: + api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \ + .AndReturn(True) + api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(self.floating_ips.list()) + if 'security_groups' in targets: + api.neutron.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + + self.mox.ReplayAll() + + quota_usages = quotas.tenant_quota_usages(self.request, + targets=targets) + + network_used = len(self.networks.list()) + subnet_used = len(self.subnets.list()) + router_used = len(self.routers.list()) + fip_used = len(self.floating_ips.list()) + sg_used = len(self.security_groups.list()) + expected = { + 'networks': {'used': network_used, 'quota': 10, + 'available': 10 - network_used}, + 'subnets': {'used': subnet_used, 'quota': 10, + 'available': 10 - subnet_used}, + 'routers': {'used': router_used, 'quota': 10, + 'available': 10 - router_used}, + 'security_groups': {'used': sg_used, 'quota': 20, + 'available': 20 - sg_used}, + 'floating_ips': {'used': fip_used, 'quota': 50, + 'available': 50 - fip_used}, + } + expected = dict((k, v) for k, v in expected.items() if k in targets) + + # Compare internal structure of usages to expected. + self.assertEqual(expected, quota_usages.usages) + # Compare available resources + self.assertAvailableQuotasEqual(expected, quota_usages.usages) diff --git a/openstack_dashboard/usage/quotas.py b/openstack_dashboard/usage/quotas.py index 7db3ddb4a9..a5e2bbd4b8 100644 --- a/openstack_dashboard/usage/quotas.py +++ b/openstack_dashboard/usage/quotas.py @@ -292,6 +292,12 @@ def get_disabled_quotas(request): return disabled_quotas +def _add_usage_if_quota_enabled(usage, name, value, disabled_quotas): + if name in disabled_quotas: + return + usage.tally(name, value) + + @profiler.trace def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id): enabled_compute_quotas = NOVA_COMPUTE_QUOTA_FIELDS - disabled_quotas @@ -310,29 +316,36 @@ def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id): else: instances, has_more = nova.server_list(request) - # Fetch deleted flavors if necessary. - flavors = dict([(f.id, f) for f in nova.flavor_list(request)]) - missing_flavors = [instance.flavor['id'] for instance in instances - if instance.flavor['id'] not in flavors] - for missing in missing_flavors: - if missing not in flavors: - try: - flavors[missing] = nova.flavor_get(request, missing) - except Exception: - flavors[missing] = {} - exceptions.handle(request, ignore=True) + _add_usage_if_quota_enabled(usages, 'instances', len(instances), + disabled_quotas) - usages.tally('instances', len(instances)) + if {'cores', 'ram'} - disabled_quotas: + # Fetch deleted flavors if necessary. + flavors = dict([(f.id, f) for f in nova.flavor_list(request)]) + missing_flavors = [instance.flavor['id'] for instance in instances + if instance.flavor['id'] not in flavors] + for missing in missing_flavors: + if missing not in flavors: + try: + flavors[missing] = nova.flavor_get(request, missing) + except Exception: + flavors[missing] = {} + exceptions.handle(request, ignore=True) - # Sum our usage based on the flavors of the instances. - for flavor in [flavors[instance.flavor['id']] for instance in instances]: - usages.tally('cores', getattr(flavor, 'vcpus', None)) - usages.tally('ram', getattr(flavor, 'ram', None)) + # Sum our usage based on the flavors of the instances. + for flavor in [flavors[instance.flavor['id']] + for instance in instances]: + _add_usage_if_quota_enabled( + usages, 'cores', getattr(flavor, 'vcpus', None), + disabled_quotas) + _add_usage_if_quota_enabled( + usages, 'ram', getattr(flavor, 'ram', None), + disabled_quotas) - # Initialize the tally if no instances have been launched yet - if len(instances) == 0: - usages.tally('cores', 0) - usages.tally('ram', 0) + # Initialize the tally if no instances have been launched yet + if len(instances) == 0: + _add_usage_if_quota_enabled(usages, 'cores', 0, disabled_quotas) + _add_usage_if_quota_enabled(usages, 'ram', 0, disabled_quotas) @profiler.trace @@ -384,21 +397,55 @@ def _get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id): snapshots = cinder.volume_snapshot_list(request) volume_usage = sum([int(v.size) for v in volumes]) snapshot_usage = sum([int(s.size) for s in snapshots]) - usages.tally('gigabytes', (snapshot_usage + volume_usage)) - usages.tally('volumes', len(volumes)) - usages.tally('snapshots', len(snapshots)) + _add_usage_if_quota_enabled( + usages, 'gigabytes', (snapshot_usage + volume_usage), + disabled_quotas) + _add_usage_if_quota_enabled( + usages, 'volumes', len(volumes), disabled_quotas) + _add_usage_if_quota_enabled( + usages, 'snapshots', len(snapshots), disabled_quotas) except cinder.cinder_exception.ClientException: msg = _("Unable to retrieve volume limit information.") exceptions.handle(request, msg) +NETWORK_QUOTA_API_KEY_MAP = { + 'floating_ips': ['floatingip', 'floating_ips'], + 'security_groups': ['security_group', 'security_groups'], + 'security_group_rules': ['security_group_rule', 'security_group_rules'], + # Singular form key is used as quota field in the Neutron API. + # We convert it explicitly here. + # NOTE(amotoki): It is better to be converted in the horizon API wrapper + # layer. Ideally the REST APIs of back-end services are consistent. + 'networks': ['network'], + 'subnets': ['subnet'], + 'ports': ['port'], + 'routers': ['router'], +} + + +def _convert_targets_to_quota_keys(targets): + quota_keys = set() + for target in targets: + if target in NETWORK_QUOTA_API_KEY_MAP: + quota_keys.update(NETWORK_QUOTA_API_KEY_MAP[target]) + continue + if target in QUOTA_FIELDS: + quota_keys.add(target) + continue + raise ValueError('"%s" is not a valid quota field name.' % target) + return quota_keys + + @profiler.trace @memoized -def tenant_quota_usages(request, tenant_id=None): +def tenant_quota_usages(request, tenant_id=None, targets=None): """Get our quotas and construct our usage object. - If no tenant_id is provided, a the request.user.project_id - is assumed to be used + :param tenant_id: Target tenant ID. If no tenant_id is provided, + a the request.user.project_id is assumed to be used. + :param targets: A list of quota names to be retrieved. + If unspecified, all quota and usage information is retrieved. """ if not tenant_id: tenant_id = request.user.project_id @@ -406,6 +453,11 @@ def tenant_quota_usages(request, tenant_id=None): disabled_quotas = get_disabled_quotas(request) usages = QuotaUsage() + if targets: + enabled_quotas = set(QUOTA_FIELDS) - disabled_quotas + enabled_quotas &= _convert_targets_to_quota_keys(targets) + disabled_quotas = set(QUOTA_FIELDS) - enabled_quotas + for quota in get_tenant_quota_data(request, disabled_quotas=disabled_quotas, tenant_id=tenant_id): diff --git a/releasenotes/notes/quota-usage-reduce-unnecessary-API-calls-1ca424e093bec135.yaml b/releasenotes/notes/quota-usage-reduce-unnecessary-API-calls-1ca424e093bec135.yaml new file mode 100644 index 0000000000..d3e63a3b78 --- /dev/null +++ b/releasenotes/notes/quota-usage-reduce-unnecessary-API-calls-1ca424e093bec135.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + Unnecessary API calls to back-end services are eliminated when checking + the quota and usage in individual panels. Each panel checks a resource + can be created by retrieving the current quota and usage for the resource. + However, the previous implementation retrieves quota and usage of unrelated + resources (For example, Nova usage is retrieved when checking a network + usage). It can be a performance problem in large deployments. + This behavior is now fixed to load quota and usage only for resources + which are really required.