quota: retrieve quota (limit) and usage at once

Previously tenant_quota_usages() uses list API operations
to count usages. It is not efficient. This commit changes
the limit APIs from nova and cinder to retrieve usages.

blueprint make-quotas-great-again
Change-Id: I2c9a479758a1dfe134e5fabf16ab02831338718d
This commit is contained in:
Akihiro Motoki 2017-10-22 22:50:42 +00:00
parent 34fb3e5b0e
commit df0a18e7a3
9 changed files with 222 additions and 239 deletions

View File

@ -967,8 +967,8 @@ def qos_specs_list(request):
@profiler.trace
@memoized
def tenant_absolute_limits(request):
limits = cinderclient(request).limits.get().absolute
def tenant_absolute_limits(request, tenant_id=None):
limits = cinderclient(request).limits.get(tenant_id=tenant_id).absolute
limits_dict = {}
for limit in limits:
if limit.value < 0:

View File

@ -908,8 +908,13 @@ def migrate_host(request, host, live_migrate=False, disk_over_commit=False,
@profiler.trace
def tenant_absolute_limits(request, reserved=False):
limits = novaclient(request).limits.get(reserved=reserved).absolute
def tenant_absolute_limits(request, reserved=False, tenant_id=None):
# Nova does not allow to specify tenant_id for non-admin users
# even if tenant_id matches a tenant_id of the user.
if tenant_id == request.user.tenant_id:
tenant_id = None
limits = novaclient(request).limits.get(reserved=reserved,
tenant_id=tenant_id).absolute
limits_dict = {}
for limit in limits:
if limit.value < 0:

View File

@ -52,8 +52,8 @@ class DeleteKeyPairs(tables.DeleteAction):
class QuotaKeypairMixin(object):
def allowed(self, request, datum=None):
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):
usages.tally('key_pairs', len(self.table.data))
if usages['key_pairs']['available'] <= 0:
if "disabled" not in self.classes:
self.classes = [c for c in self.classes] + ['disabled']
self.verbose_name = string_concat(self.verbose_name, ' ',

View File

@ -337,9 +337,9 @@ class UsageViewTests(test.TestCase):
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertIsInstance(usages, usage.ProjectUsage)
if cinder_enabled:
self.assertEqual(usages.limits['totalVolumesUsed'], 1)
self.assertEqual(usages.limits['maxTotalVolumes'], 10)
self.assertEqual(usages.limits['totalGigabytesUsed'], 5)
self.assertEqual(usages.limits['totalVolumesUsed'], 4)
self.assertEqual(usages.limits['maxTotalVolumes'], 20)
self.assertEqual(usages.limits['totalGigabytesUsed'], 400)
self.assertEqual(usages.limits['maxTotalVolumeGigabytes'], 1000)
else:
self.assertNotIn('totalVolumesUsed', usages.limits)

View File

@ -333,10 +333,17 @@ def data(TEST):
)
)
# Cinder Limits
limits = {"absolute": {"totalVolumesUsed": 1,
"totalGigabytesUsed": 5,
"maxTotalVolumeGigabytes": 1000,
"maxTotalVolumes": 10}}
limits = {
"absolute": {
"totalVolumesUsed": 4,
"totalGigabytesUsed": 400,
'totalSnapshotsUsed': 3,
"maxTotalVolumes": 20,
"maxTotalVolumeGigabytes": 1000,
'maxTotalSnapshots': 10,
}
}
TEST.cinder_limits = limits
# QOS Specs

View File

@ -340,10 +340,10 @@ def data(TEST):
"maxTotalInstances": 10,
"maxTotalKeypairs": 100,
"maxTotalRAMSize": 10000,
"totalCoresUsed": 0,
"totalInstancesUsed": 0,
"totalCoresUsed": 2,
"totalInstancesUsed": 2,
"totalKeyPairsUsed": 0,
"totalRAMUsed": 0,
"totalRAMUsed": 1024,
"totalSecurityGroupsUsed": 0}}
TEST.limits = limits

View File

@ -341,7 +341,8 @@ class ComputeApiTests(test.APITestCase):
novaclient = self.stub_novaclient()
novaclient.limits = self.mox.CreateMockAnything()
novaclient.limits.get(reserved=True).AndReturn(limits)
novaclient.limits.get(reserved=True,
tenant_id=None).AndReturn(limits)
self.mox.ReplayAll()
ret_val = api.nova.tenant_absolute_limits(self.request, reserved=True)

View File

@ -67,6 +67,24 @@ class QuotaTests(test.APITestCase):
'quota': 1000}})
return usages
def get_usages_from_limits(self, with_volume=True, with_compute=True,
nova_quotas_enabled=True):
usages = {}
if with_compute and nova_quotas_enabled:
usages.update({
'instances': {'available': 8, 'used': 2, 'quota': 10},
'cores': {'available': 18, 'used': 2, 'quota': 20},
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
'key_pairs': {'quota': 100},
})
if with_volume:
usages.update({
'volumes': {'available': 16, 'used': 4, 'quota': 20},
'gigabytes': {'available': 600, 'used': 400, 'quota': 1000},
'snapshots': {'available': 7, 'used': 3, 'quota': 10},
})
return usages
def assertAvailableQuotasEqual(self, expected_usages, actual_usages):
expected_available = {key: value['available'] for key, value in
expected_usages.items() if 'available' in value}
@ -74,12 +92,9 @@ class QuotaTests(test.APITestCase):
actual_usages.items() if 'available' in value}
self.assertEqual(expected_available, actual_available)
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',
cinder: ('tenant_absolute_limits',
'is_volume_service_enabled')})
def test_tenant_quota_usages_with_id(self):
tenant_id = 3
@ -88,48 +103,35 @@ class QuotaTests(test.APITestCase):
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True)
servers = [s for s in self.servers.list() if s.tenant_id == tenant_id]
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
opts = {'tenant_id': tenant_id,
'all_tenants': True}
api.nova.server_list(IsA(http.HttpRequest), search_opts=opts) \
.AndReturn([servers, False])
api.nova.tenant_quota_get(IsA(http.HttpRequest), tenant_id) \
.AndReturn(self.quotas.first())
opts = {'all_tenants': 1,
'project_id': 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), tenant_id) \
.AndReturn(self.cinder_quotas.first())
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
api.cinder.tenant_absolute_limits(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request,
tenant_id=tenant_id)
expected_output = self.get_usages(
nova_quotas_enabled=True, with_volume=True,
with_compute=True, tenant_id=tenant_id)
expected_output = self.get_usages_from_limits(
with_volume=True, with_compute=True)
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
# Compare available resources
self.assertAvailableQuotasEqual(expected_output, quota_usages.usages)
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',
cinder: ('tenant_absolute_limits',
'is_volume_service_enabled')})
def _test_tenant_quota_usages(self, nova_quotas_enabled=True,
with_compute=True, with_volume=True):
tenant_id = '1'
cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(
with_volume)
api.base.is_service_enabled(IsA(http.HttpRequest),
@ -139,30 +141,22 @@ class QuotaTests(test.APITestCase):
).MultipleTimes().AndReturn(with_compute)
if with_compute:
if nova_quotas_enabled:
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn([servers, False])
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
if with_volume:
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())
api.cinder.tenant_absolute_limits(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages(
nova_quotas_enabled=nova_quotas_enabled, with_volume=with_volume,
expected_output = self.get_usages_from_limits(
nova_quotas_enabled=nova_quotas_enabled,
with_volume=with_volume,
with_compute=with_compute)
# Compare internal structure of usages to expected.
@ -198,14 +192,11 @@ class QuotaTests(test.APITestCase):
quotas.NOVA_QUOTA_FIELDS)
self.assertItemsEqual(result_quotas, expected_quotas)
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
api.cinder: ('is_volume_service_enabled',)})
def test_tenant_quota_usages_without_volume(self):
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
tenant_id = self.request.user.tenant_id
api.cinder.is_volume_service_enabled(
IsA(http.HttpRequest)
@ -214,17 +205,15 @@ class QuotaTests(test.APITestCase):
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn([servers, False])
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages(with_volume=False)
expected_output = self.get_usages_from_limits(with_volume=False)
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
@ -234,9 +223,7 @@ class QuotaTests(test.APITestCase):
self.assertIn('ram', quota_usages)
self.assertIsNotNone(quota_usages.get('ram'))
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
api.cinder: ('is_volume_service_enabled',)})
def test_tenant_quota_usages_no_instances_running(self):
@ -247,16 +234,15 @@ class QuotaTests(test.APITestCase):
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([[], False])
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id='1').AndReturn(self.limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages(with_volume=False)
expected_output = self.get_usages_from_limits(with_volume=False)
expected_output.update({
'ram': {'available': 10000, 'used': 0, 'quota': 10000},
@ -266,18 +252,14 @@ class QuotaTests(test.APITestCase):
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',
cinder: ('tenant_absolute_limits',
'is_volume_service_enabled')})
def test_tenant_quota_usages_unlimited_quota(self):
tenant_id = '1'
inf_quota = self.quotas.first()
inf_quota['ram'] = -1
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
cinder.is_volume_service_enabled(
IsA(http.HttpRequest)
@ -286,23 +268,19 @@ class QuotaTests(test.APITestCase):
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(inf_quota)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
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())
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
api.cinder.tenant_absolute_limits(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages()
expected_output = self.get_usages_from_limits()
expected_output.update({'ram': {'available': float("inf"),
'used': 1024,
'quota': float("inf")}})
@ -310,17 +288,12 @@ class QuotaTests(test.APITestCase):
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
@test.create_stubs({api.nova: ('server_list',
'flavor_list',
'tenant_quota_get',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',
cinder: ('tenant_absolute_limits',
'is_volume_service_enabled')})
def test_tenant_quota_usages_neutron_fip_disabled(self):
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
tenant_id = '1'
cinder.is_volume_service_enabled(
IsA(http.HttpRequest)
).AndReturn(True)
@ -328,38 +301,22 @@ class QuotaTests(test.APITestCase):
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
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())
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
api.cinder.tenant_absolute_limits(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages()
expected_output = self.get_usages_from_limits()
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
@test.create_stubs({cinder: ('volume_list',),
exceptions: ('handle',)})
def test_get_tenant_volume_usages_cinder_exception(self):
cinder.volume_list(IsA(http.HttpRequest)) \
.AndRaise(cinder.cinder_exception.ClientException('test'))
exceptions.handle(IsA(http.HttpRequest),
_("Unable to retrieve volume limit information."))
self.mox.ReplayAll()
quotas._get_tenant_volume_usages(self.request, {}, set(), None)
@test.create_stubs({api.base: ('is_service_enabled',),
api.cinder: ('tenant_quota_get',
'is_volume_service_enabled'),
@ -439,17 +396,15 @@ class QuotaTests(test.APITestCase):
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',),
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
cinder: ('volume_list', 'volume_snapshot_list',
'tenant_quota_get',
cinder: ('tenant_absolute_limits',
'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):
tenant_id = self.request.user.tenant_id
cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.AndReturn(False)
@ -457,32 +412,22 @@ class QuotaTests(test.APITestCase):
.MultipleTimes().AndReturn(True)
if use_compute_call:
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn([servers, False])
if use_flavor_list:
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_absolute_limits(
IsA(http.HttpRequest),
reserved=True,
tenant_id=tenant_id).AndReturn(self.limits['absolute'])
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())
api.cinder.tenant_absolute_limits(
IsA(http.HttpRequest),
tenant_id).AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
quota_usages = quotas.tenant_quota_usages(self.request,
targets=targets)
expected = self.get_usages()
expected = self.get_usages_from_limits()
expected = dict((k, v) for k, v in expected.items() if k in targets)
# Compare internal structure of usages to expected.

View File

@ -44,10 +44,38 @@ NOVA_COMPUTE_QUOTA_FIELDS = {
# are not considered.
NOVA_QUOTA_FIELDS = NOVA_COMPUTE_QUOTA_FIELDS
NOVA_QUOTA_LIMIT_MAP = {
'instances': {
'limit': 'maxTotalInstances',
'usage': 'totalInstancesUsed'
},
'cores': {
'limit': 'maxTotalCores',
'usage': 'totalCoresUsed'
},
'ram': {
'limit': 'maxTotalRAMSize',
'usage': 'totalRAMUsed'
},
'key_pairs': {
'limit': 'maxTotalKeypairs',
'usage': None
},
}
CINDER_QUOTA_FIELDS = {"volumes",
"snapshots",
"gigabytes"}
CINDER_QUOTA_LIMIT_MAP = {
'volumes': {'usage': 'totalVolumesUsed',
'limit': 'maxTotalVolumes'},
'gigabytes': {'usage': 'totalGigabytesUsed',
'limit': 'maxTotalVolumeGigabytes'},
'snapshots': {'usage': 'totalSnapshotsUsed',
'limit': 'maxTotalSnapshots'},
}
NEUTRON_QUOTA_FIELDS = {"network",
"subnet",
"port",
@ -198,6 +226,12 @@ def get_tenant_quota_data(request, disabled_quotas=None, tenant_id=None):
if not (NEUTRON_QUOTA_FIELDS - disabled_quotas):
return qs
_get_neutron_quota_data(request, qs, disabled_quotas, tenant_id)
return qs
def _get_neutron_quota_data(request, qs, disabled_quotas, tenant_id):
tenant_id = tenant_id or request.user.tenant_id
neutron_quotas = neutron.tenant_quota_get(request, tenant_id)
@ -232,6 +266,11 @@ def get_tenant_quota_data(request, disabled_quotas=None, tenant_id=None):
return qs
# TOOD(amotoki): Do not use neutron specific quota field names.
# At now, quota names from nova-network are used in the dashboard code,
# but get_disabled_quotas() returns quota names from neutron API.
# It is confusing and makes the code complicated. They should be push away.
# Check Identity Project panel and System Defaults panel too.
@profiler.trace
def get_disabled_quotas(request):
# We no longer supports nova network, so we always disable
@ -268,10 +307,19 @@ def get_disabled_quotas(request):
return disabled_quotas
def _add_usage_if_quota_enabled(usage, name, value, disabled_quotas):
if name in disabled_quotas:
def _add_limit_and_usage(usages, name, limit, usage, disabled_quotas):
if name not in disabled_quotas:
usages.add_quota(base.Quota(name, limit))
if usage is not None:
usages.tally(name, usage)
def _add_limit_and_usage_neutron(usages, name, quota_name,
detail, disabled_quotas):
if quota_name in disabled_quotas:
return
usage.tally(name, value)
usages.add_quota(base.Quota(name, detail['limit']))
usages.tally(name, detail['used'] + detail['reserved'])
@profiler.trace
@ -280,50 +328,25 @@ def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id):
if not enabled_compute_quotas:
return
# Unlike the other services it can be the case that nova is enabled but
# doesn't support quotas, in which case we still want to get usage info,
# so don't rely on '"instances" in disabled_quotas' as elsewhere
if not base.is_service_enabled(request, 'compute'):
return
if tenant_id and tenant_id != request.user.project_id:
# all_tenants is required when querying about any project the user is
# not currently scoped to
instances, has_more = nova.server_list(
request, search_opts={'tenant_id': tenant_id, 'all_tenants': True})
else:
instances, has_more = nova.server_list(request)
try:
limits = nova.tenant_absolute_limits(request, reserved=True,
tenant_id=tenant_id)
except nova.nova_exceptions.ClientException:
msg = _("Unable to retrieve compute limit information.")
exceptions.handle(request, msg)
_add_usage_if_quota_enabled(usages, 'instances', len(instances),
disabled_quotas)
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]:
_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:
_add_usage_if_quota_enabled(usages, 'cores', 0, disabled_quotas)
_add_usage_if_quota_enabled(usages, 'ram', 0, disabled_quotas)
for quota_name, limit_keys in NOVA_QUOTA_LIMIT_MAP.items():
if limit_keys['usage']:
usage = limits[limit_keys['usage']]
else:
usage = None
_add_limit_and_usage(usages, quota_name,
limits[limit_keys['limit']],
usage,
disabled_quotas)
@profiler.trace
@ -332,6 +355,11 @@ def _get_tenant_network_usages(request, usages, disabled_quotas, tenant_id):
if not enabled_quotas:
return
qs = base.QuotaSet()
_get_neutron_quota_data(request, qs, disabled_quotas, tenant_id)
for quota in qs:
usages.add_quota(quota)
# NOTE(amotoki): floatingip is Neutron quota and floating_ips is
# Nova quota. We need to check both.
if {'floatingip', 'floating_ips'} & enabled_quotas:
@ -367,41 +395,35 @@ def _get_tenant_network_usages(request, usages, disabled_quotas, tenant_id):
@profiler.trace
def _get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id):
if CINDER_QUOTA_FIELDS - disabled_quotas:
try:
if tenant_id:
opts = {'all_tenants': 1, 'project_id': tenant_id}
volumes = cinder.volume_list(request, opts)
snapshots = cinder.volume_snapshot_list(request, opts)
else:
volumes = cinder.volume_list(request)
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])
_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)
enabled_volume_quotas = CINDER_QUOTA_FIELDS - disabled_quotas
if not enabled_volume_quotas:
return
try:
limits = cinder.tenant_absolute_limits(request, tenant_id)
except cinder.cinder_exception.ClientException:
msg = _("Unable to retrieve volume limit information.")
exceptions.handle(request, msg)
for quota_name, limit_keys in CINDER_QUOTA_LIMIT_MAP.items():
_add_limit_and_usage(usages, quota_name,
limits[limit_keys['limit']],
limits[limit_keys['usage']],
disabled_quotas)
# 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.
NETWORK_QUOTA_API_KEY_MAP = {
'floating_ips': ['floatingip'],
'security_groups': ['security_group'],
'security_group_rules': ['security_group_rule'],
# 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'],
'security_group_rules': ['security_group_rule'],
'security_groups': ['security_group'],
'subnets': ['subnet'],
}
@ -418,6 +440,9 @@ def _convert_targets_to_quota_keys(targets):
return quota_keys
# TODO(amotoki): Merge tenant_quota_usages and tenant_limit_usages.
# These two functions are similar. There seems no reason to have both.
@profiler.trace
@memoized
def tenant_quota_usages(request, tenant_id=None, targets=None):
@ -439,12 +464,6 @@ def tenant_quota_usages(request, tenant_id=None, targets=None):
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):
usages.add_quota(quota)
# Get our usages.
_get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id)
_get_tenant_network_usages(request, usages, disabled_quotas, tenant_id)
_get_tenant_volume_usages(request, usages, disabled_quotas, tenant_id)
@ -472,6 +491,12 @@ def tenant_limit_usages(request):
msg = _("Unable to retrieve volume limit information.")
exceptions.handle(request, msg)
# TODO(amotoki): Support neutron quota details extensions
# which returns limit/usage/reserved per resource.
# Note that the data format is different from nova/cinder limit API.
# https://developer.openstack.org/
# api-ref/network/v2/#quotas-details-extension-quota-details
return limits