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):
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.')

View File

@ -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']

View File

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

View File

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

View File

@ -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),

View File

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

View File

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

View File

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

View File

@ -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()

View File

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

View File

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

View File

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

View File

@ -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()

View File

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

View File

@ -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()

View File

@ -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"]

View File

@ -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()

View File

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

View File

@ -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):

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.