Retrieve quota and usage only for resources really required

tenant_quota_usage() is used to retrieve quota and usage
to determine if a resource can be created.
However, tenant_quota_usage retrieves quota and usage for
all resources and it can be a performance problem.

This commit allows to load quota and usage only for resources
which are actually required.

Closes-Bug: #1675504
Change-Id: Iab7322a337a451a1a040cc2f4b55cc319b1ffc4c
This commit is contained in:
Akihiro Motoki 2017-04-12 18:10:20 +00:00
parent 41d6a5352e
commit 359467b401
20 changed files with 351 additions and 105 deletions

View File

@ -38,7 +38,8 @@ class FloatingIpAllocate(forms.SelfHandlingForm):
def handle(self, request, data): def handle(self, request, data):
try: try:
# Prevent allocating more IP than the quota allows # 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: if usages['floating_ips']['available'] <= 0:
error_message = _('You are already using all of your available' error_message = _('You are already using all of your available'
' floating IPs.') ' floating IPs.')

View File

@ -47,7 +47,8 @@ class AllocateIP(tables.LinkAction):
return shortcuts.redirect('horizon:project:floating_ips:index') return shortcuts.redirect('horizon:project:floating_ips:index')
def allowed(self, request, fip=None): 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 usages['floating_ips']['available'] <= 0:
if "disabled" not in self.classes: if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled'] self.classes = [c for c in self.classes] + ['disabled']

View File

@ -266,7 +266,7 @@ class FloatingIpViewTests(test.TestCase):
IsA(http.HttpRequest), detailed=False) \ IsA(http.HttpRequest), detailed=False) \
.AndReturn([self.servers.list(), False]) .AndReturn([self.servers.list(), False])
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)).MultipleTimes() \ IsA(http.HttpRequest), targets=['floating_ips']).MultipleTimes() \
.AndReturn(quota_data) .AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -304,7 +304,7 @@ class FloatingIpViewTests(test.TestCase):
IsA(http.HttpRequest), detailed=False) \ IsA(http.HttpRequest), detailed=False) \
.AndReturn([self.servers.list(), False]) .AndReturn([self.servers.list(), False])
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)).MultipleTimes() \ IsA(http.HttpRequest), targets=['floating_ips']).MultipleTimes() \
.AndReturn(quota_data) .AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -318,38 +318,22 @@ class FloatingIpViewTests(test.TestCase):
self.assertEqual('Allocate IP To Project (Quota exceeded)', self.assertEqual('Allocate IP To Project (Quota exceeded)',
six.text_type(allocate_action.verbose_name)) six.text_type(allocate_action.verbose_name))
@test.create_stubs({api.nova: ('tenant_quota_get', 'flavor_list', @test.create_stubs({api.neutron: ('floating_ip_pools_list',
'server_list'),
api.neutron: ('floating_ip_pools_list',
'floating_ip_supported', 'floating_ip_supported',
'security_group_list',
'tenant_floating_ip_list', 'tenant_floating_ip_list',
'is_extension_supported', 'is_extension_supported',
'is_router_enabled', 'is_router_enabled',
'tenant_quota_get', 'tenant_quota_get'),
'network_list',
'router_list',
'subnet_list'),
api.base: ('is_service_enabled',), api.base: ('is_service_enabled',),
api.cinder: ('is_volume_service_enabled',)}) api.cinder: ('is_volume_service_enabled',)})
@test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True}) @test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True})
def test_correct_quotas_displayed(self): 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)) \ api.cinder.is_volume_service_enabled(IsA(http.HttpRequest)) \
.AndReturn(False) .AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \ api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.MultipleTimes().AndReturn(True) .MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \ api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True) .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( api.neutron.is_extension_supported(
IsA(http.HttpRequest), 'security-group').AndReturn(True) IsA(http.HttpRequest), 'security-group').AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest), 'quotas') \ api.neutron.is_extension_supported(IsA(http.HttpRequest), 'quotas') \
@ -358,23 +342,12 @@ class FloatingIpViewTests(test.TestCase):
.AndReturn(True) .AndReturn(True)
api.neutron.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \ api.neutron.tenant_quota_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(self.neutron_quotas.first()) .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)) \ api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True) .AndReturn(True)
api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \ api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(self.floating_ips.list()) .MultipleTimes().AndReturn(self.floating_ips.list())
api.neutron.floating_ip_pools_list(IsA(http.HttpRequest)) \ api.neutron.floating_ip_pools_list(IsA(http.HttpRequest)) \
.AndReturn(self.pools.list()) .AndReturn(self.pools.list())
api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('%s:allocate' % NAMESPACE) url = reverse('%s:allocate' % NAMESPACE)

View File

@ -61,7 +61,8 @@ class AllocateView(forms.ModalFormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(AllocateView, self).get_context_data(**kwargs) context = super(AllocateView, self).get_context_data(**kwargs)
try: try:
context['usages'] = quotas.tenant_quota_usages(self.request) context['usages'] = quotas.tenant_quota_usages(
self.request, targets=['floating_ips'])
except Exception: except Exception:
exceptions.handle(self.request) exceptions.handle(self.request)
return context return context

View File

@ -1995,7 +1995,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
disk_config=disk_config_value, disk_config=disk_config_value,
config_drive=config_drive_value, config_drive=config_drive_value,
scheduler_hints=scheduler_hints) 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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -2123,7 +2125,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
disk_config=u'AUTO', disk_config=u'AUTO',
config_drive=True, config_drive=True,
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) .AndReturn(quota_usages)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -2208,7 +2212,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest), cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=SNAPSHOT_SEARCH_OPTS) \ search_opts=SNAPSHOT_SEARCH_OPTS) \
.AndReturn([]) .AndReturn([])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ quotas.tenant_quota_usages(
IsA(http.HttpRequest),
targets=['instances', 'cores', 'ram', 'volumes']) \
.AndReturn(quota_usages) .AndReturn(quota_usages)
api.nova.extension_supported('BlockDeviceMappingV2Boot', api.nova.extension_supported('BlockDeviceMappingV2Boot',
@ -2312,7 +2318,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
cinder.volume_snapshot_list(IsA(http.HttpRequest), cinder.volume_snapshot_list(IsA(http.HttpRequest),
search_opts=SNAPSHOT_SEARCH_OPTS) \ search_opts=SNAPSHOT_SEARCH_OPTS) \
.AndReturn([]) .AndReturn([])
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \ quotas.tenant_quota_usages(
IsA(http.HttpRequest),
targets=['instances', 'cores', 'ram', 'volumes']) \
.AndReturn(quota_usages) .AndReturn(quota_usages)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -2426,7 +2434,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
disk_config=u'AUTO', disk_config=u'AUTO',
config_drive=True, config_drive=True,
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) .AndReturn(quota_usages)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -2496,7 +2506,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
'status': 'active'}) \ 'status': 'active'}) \
.AndReturn([[], False, False]) .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) .AndReturn(quota_usages)
self._mock_neutron_network_and_port_list() self._mock_neutron_network_and_port_list()
@ -2664,7 +2676,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
config_drive=False, config_drive=False,
scheduler_hints={}) \ scheduler_hints={}) \
.AndRaise(self.exceptions.keystone) .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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -2748,7 +2762,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ quotas.tenant_limit_usages(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute']) .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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -2835,7 +2851,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ quotas.tenant_limit_usages(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute']) .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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -2930,7 +2948,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ quotas.tenant_limit_usages(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute']) .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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -3075,7 +3095,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
quotas.tenant_limit_usages( quotas.tenant_limit_usages(
IsA(http.HttpRequest)).AndReturn(self.limits['absolute']) IsA(http.HttpRequest)).AndReturn(self.limits['absolute'])
quotas.tenant_quota_usages( 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( api.nova.flavor_list(
IsA(http.HttpRequest)).AndReturn(self.flavors.list()) IsA(http.HttpRequest)).AndReturn(self.flavors.list())
@ -3202,7 +3224,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
quotas.tenant_limit_usages(IsA(http.HttpRequest)) \ quotas.tenant_limit_usages(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute']) .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) .AndReturn(quota_usages)
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .AndReturn(self.flavors.list())
@ -3405,7 +3429,9 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
api.nova.flavor_list(IsA(http.HttpRequest)) \ api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list()) .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) .AndReturn(quota_usages)
api.nova.server_create(IsA(http.HttpRequest), api.nova.server_create(IsA(http.HttpRequest),

View File

@ -207,7 +207,9 @@ class SetInstanceDetailsAction(workflows.Action):
count = cleaned_data.get('count', 1) count = cleaned_data.get('count', 1)
# Prevent launching more instances than the quota allows # 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'] available_count = usages['instances']['available']
if available_count < count: if available_count < count:
msg = (_('The requested instance(s) cannot be launched ' msg = (_('The requested instance(s) cannot be launched '

View File

@ -50,7 +50,7 @@ class DeleteKeyPairs(tables.DeleteAction):
class QuotaKeypairMixin(object): class QuotaKeypairMixin(object):
def allowed(self, request, datum=None): 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) count = len(self.table.data)
if (usages.get('key_pairs') and usages['key_pairs']['quota'] <= count): if (usages.get('key_pairs') and usages['key_pairs']['quota'] <= count):
if "disabled" not in self.classes: if "disabled" not in self.classes:

View File

@ -42,7 +42,8 @@ class KeyPairTests(test.TestCase):
keypairs = self.keypairs.list() keypairs = self.keypairs.list()
quota_data = self.quota_usages.first() 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) .AndReturn(quota_data)
api.nova.keypair_list(IsA(http.HttpRequest)) \ api.nova.keypair_list(IsA(http.HttpRequest)) \
.AndReturn(keypairs) .AndReturn(keypairs)

View File

@ -198,8 +198,14 @@ class NetworkTopologyCreateTests(test.TestCase):
quota_data['instances']['available'] = instances_quota quota_data['instances']['available'] = instances_quota
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['instances']
.MultipleTimes().AndReturn(quota_data) ).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() self.mox.ReplayAll()

View File

@ -18,7 +18,7 @@ from openstack_dashboard.usage import quotas
def _quota_exceeded(request, quota): 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) available = usages.get(quota, {}).get('available', 1)
return available <= 0 return available <= 0

View File

@ -103,7 +103,7 @@ class CreateSubnet(SubnetPolicyTargetMixin, CheckNetworkEditable,
return reverse(self.url, args=(network_id,)) return reverse(self.url, args=(network_id,))
def allowed(self, request, datum=None): 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 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages["subnets'] is empty # usages["subnets'] is empty

View File

@ -92,7 +92,7 @@ class CreateNetwork(tables.LinkAction):
policy_rules = (("network", "create_network"),) policy_rules = (("network", "create_network"),)
def allowed(self, request, datum=None): 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 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages["networks"] is empty # usages["networks"] is empty
if usages.get('networks', {}).get('available', 1) <= 0: if usages.get('networks', {}).get('available', 1) <= 0:
@ -129,7 +129,7 @@ class CreateSubnet(policy.PolicyTargetMixin, CheckNetworkEditable,
("network:project_id", "tenant_id"),) ("network:project_id", "tenant_id"),)
def allowed(self, request, datum=None): 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 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages["subnets'] is empty # usages["subnets'] is empty
if usages.get('subnets', {}).get('available', 1) <= 0: if usages.get('subnets', {}).get('available', 1) <= 0:

View File

@ -132,7 +132,10 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
quota_data['subnets']['available'] = 5 quota_data['subnets']['available'] = 5
self._stub_net_list() self._stub_net_list()
quotas.tenant_quota_usages( 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) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -151,7 +154,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
tenant_id=self.tenant.id, tenant_id=self.tenant.id,
shared=False).MultipleTimes().AndRaise(self.exceptions.neutron) shared=False).MultipleTimes().AndRaise(self.exceptions.neutron)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['networks']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -190,7 +193,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
.AndReturn(mac_learning) .AndReturn(mac_learning)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['subnets']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -217,7 +220,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['subnets']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -288,7 +291,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['subnets']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -330,7 +333,7 @@ class NetworkTests(test.TestCase, NetworkStubMixin):
'mac-learning')\ 'mac-learning')\
.AndReturn(mac_learning) .AndReturn(mac_learning)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['subnets']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -992,7 +995,10 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
self._stub_net_list() self._stub_net_list()
quotas.tenant_quota_usages( 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) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1018,7 +1024,10 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
self._stub_net_list() self._stub_net_list()
quotas.tenant_quota_usages( 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) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -1096,7 +1105,7 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
IsA(http.HttpRequest), 'mac-learning')\ IsA(http.HttpRequest), 'mac-learning')\
.AndReturn(False) .AndReturn(False)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['subnets']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -96,7 +96,7 @@ class CreateRouter(tables.LinkAction):
policy_rules = (("network", "create_router"),) policy_rules = (("network", "create_router"),)
def allowed(self, request, datum=None): 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 # when Settings.OPENSTACK_NEUTRON_NETWORK['enable_quotas'] = False
# usages['routers'] is empty # usages['routers'] is empty
if usages.get('routers', {}).get('available', 1) <= 0: if usages.get('routers', {}).get('available', 1) <= 0:

View File

@ -93,7 +93,7 @@ class RouterTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
self.mox.ReplayAll() self.mox.ReplayAll()
@ -113,7 +113,7 @@ class RouterTests(RouterMixin, test.TestCase):
tenant_id=self.tenant.id).MultipleTimes().AndRaise( tenant_id=self.tenant.id).MultipleTimes().AndRaise(
self.exceptions.neutron) self.exceptions.neutron)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
self.mox.ReplayAll() self.mox.ReplayAll()
@ -133,7 +133,7 @@ class RouterTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).MultipleTimes().AndReturn([router]) tenant_id=self.tenant.id).MultipleTimes().AndReturn([router])
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list(alter_ids=True) self._mock_external_network_list(alter_ids=True)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -177,7 +177,7 @@ class RouterTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
api.neutron.router_list( api.neutron.router_list(
@ -215,7 +215,7 @@ class RouterTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
api.neutron.router_list( api.neutron.router_list(
@ -801,7 +801,7 @@ class RouterViewTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
@ -828,7 +828,7 @@ class RouterViewTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()
@ -855,7 +855,7 @@ class RouterViewTests(RouterMixin, test.TestCase):
IsA(http.HttpRequest), IsA(http.HttpRequest),
tenant_id=self.tenant.id).AndReturn(self.routers.list()) tenant_id=self.tenant.id).AndReturn(self.routers.list())
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)) \ IsA(http.HttpRequest), targets=['routers']) \
.MultipleTimes().AndReturn(quota_data) .MultipleTimes().AndReturn(quota_data)
self._mock_external_network_list() self._mock_external_network_list()

View File

@ -63,7 +63,8 @@ class CreateGroup(tables.LinkAction):
icon = "plus" icon = "plus"
def allowed(self, request, security_group=None): 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 usages['security_groups'].get('available', 1) <= 0:
if "disabled" not in self.classes: if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ["disabled"] self.classes = [c for c in self.classes] + ["disabled"]

View File

@ -74,7 +74,9 @@ class SecurityGroupsViewTests(test.TestCase):
api.neutron.security_group_list(IsA(http.HttpRequest)) \ api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(sec_groups) .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) .AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -107,7 +109,8 @@ class SecurityGroupsViewTests(test.TestCase):
IsA(http.HttpRequest)) \ IsA(http.HttpRequest)) \
.AndReturn(sec_groups) .AndReturn(sec_groups)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)).MultipleTimes() \ IsA(http.HttpRequest),
targets=['security_groups']).MultipleTimes() \
.AndReturn(quota_data) .AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -140,7 +143,8 @@ class SecurityGroupsViewTests(test.TestCase):
IsA(http.HttpRequest)) \ IsA(http.HttpRequest)) \
.AndReturn(sec_groups) .AndReturn(sec_groups)
quotas.tenant_quota_usages( quotas.tenant_quota_usages(
IsA(http.HttpRequest)).MultipleTimes() \ IsA(http.HttpRequest),
targets=['security_groups']).MultipleTimes() \
.AndReturn(quota_data) .AndReturn(quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -412,3 +412,161 @@ class QuotaTests(test.APITestCase):
expected = set(['floating_ips', 'fixed_ips', 'security_groups', expected = set(['floating_ips', 'fixed_ips', 'security_groups',
'security_group_rules', 'router', 'floatingip']) 'security_group_rules', 'router', 'floatingip'])
self.assertEqual(expected, disabled_quotas) 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)

View File

@ -292,6 +292,12 @@ def get_disabled_quotas(request):
return disabled_quotas 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 @profiler.trace
def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id): def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id):
enabled_compute_quotas = NOVA_COMPUTE_QUOTA_FIELDS - disabled_quotas enabled_compute_quotas = NOVA_COMPUTE_QUOTA_FIELDS - disabled_quotas
@ -310,6 +316,10 @@ def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id):
else: else:
instances, has_more = nova.server_list(request) instances, has_more = nova.server_list(request)
_add_usage_if_quota_enabled(usages, 'instances', len(instances),
disabled_quotas)
if {'cores', 'ram'} - disabled_quotas:
# Fetch deleted flavors if necessary. # Fetch deleted flavors if necessary.
flavors = dict([(f.id, f) for f in nova.flavor_list(request)]) flavors = dict([(f.id, f) for f in nova.flavor_list(request)])
missing_flavors = [instance.flavor['id'] for instance in instances missing_flavors = [instance.flavor['id'] for instance in instances
@ -322,17 +332,20 @@ def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id):
flavors[missing] = {} flavors[missing] = {}
exceptions.handle(request, ignore=True) exceptions.handle(request, ignore=True)
usages.tally('instances', len(instances))
# Sum our usage based on the flavors of the instances. # Sum our usage based on the flavors of the instances.
for flavor in [flavors[instance.flavor['id']] for instance in instances]: for flavor in [flavors[instance.flavor['id']]
usages.tally('cores', getattr(flavor, 'vcpus', None)) for instance in instances]:
usages.tally('ram', getattr(flavor, 'ram', None)) _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 # Initialize the tally if no instances have been launched yet
if len(instances) == 0: if len(instances) == 0:
usages.tally('cores', 0) _add_usage_if_quota_enabled(usages, 'cores', 0, disabled_quotas)
usages.tally('ram', 0) _add_usage_if_quota_enabled(usages, 'ram', 0, disabled_quotas)
@profiler.trace @profiler.trace
@ -384,21 +397,55 @@ def _get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id):
snapshots = cinder.volume_snapshot_list(request) snapshots = cinder.volume_snapshot_list(request)
volume_usage = sum([int(v.size) for v in volumes]) volume_usage = sum([int(v.size) for v in volumes])
snapshot_usage = sum([int(s.size) for s in snapshots]) snapshot_usage = sum([int(s.size) for s in snapshots])
usages.tally('gigabytes', (snapshot_usage + volume_usage)) _add_usage_if_quota_enabled(
usages.tally('volumes', len(volumes)) usages, 'gigabytes', (snapshot_usage + volume_usage),
usages.tally('snapshots', len(snapshots)) 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: except cinder.cinder_exception.ClientException:
msg = _("Unable to retrieve volume limit information.") msg = _("Unable to retrieve volume limit information.")
exceptions.handle(request, msg) 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 @profiler.trace
@memoized @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. """Get our quotas and construct our usage object.
If no tenant_id is provided, a the request.user.project_id :param tenant_id: Target tenant ID. If no tenant_id is provided,
is assumed to be used 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: if not tenant_id:
tenant_id = request.user.project_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) disabled_quotas = get_disabled_quotas(request)
usages = QuotaUsage() 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, for quota in get_tenant_quota_data(request,
disabled_quotas=disabled_quotas, disabled_quotas=disabled_quotas,
tenant_id=tenant_id): tenant_id=tenant_id):

View File

@ -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.