Merge "quota: retrieve quota (limit) and usage at once"
This commit is contained in:
commit
24cc9c75fb
@ -970,8 +970,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:
|
||||
|
@ -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:
|
||||
|
@ -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, ' ',
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user