Merge "Pay attention to Nova disabled quotas defined in a config file"
This commit is contained in:
commit
dc015f556c
@ -781,6 +781,7 @@ Default::
|
||||
'can_set_mount_point': False,
|
||||
'can_set_password': False,
|
||||
'requires_keypair': False,
|
||||
'enable_quotas': True
|
||||
}
|
||||
|
||||
A dictionary containing settings which can be used to identify the
|
||||
@ -797,6 +798,9 @@ an administrator password when launching or rebuilding an instance.
|
||||
Setting ``requires_keypair`` to ``True`` will require users to select
|
||||
a key pair when launching an instance.
|
||||
|
||||
Setting ``enable_quotas`` to ``False`` will make Horizon treat all Nova
|
||||
quotas as disabled, thus it won't try to modify them. By default, quotas are
|
||||
enabled.
|
||||
|
||||
``OPENSTACK_IMAGE_BACKEND``
|
||||
---------------------------
|
||||
|
@ -811,7 +811,8 @@ def tenant_quota_get(request, tenant_id):
|
||||
|
||||
|
||||
def tenant_quota_update(request, tenant_id, **kwargs):
|
||||
novaclient(request).quotas.update(tenant_id, **kwargs)
|
||||
if kwargs:
|
||||
novaclient(request).quotas.update(tenant_id, **kwargs)
|
||||
|
||||
|
||||
def default_quota_get(request, tenant_id):
|
||||
@ -1083,3 +1084,8 @@ def can_set_mount_point():
|
||||
def requires_keypair():
|
||||
features = getattr(settings, 'OPENSTACK_HYPERVISOR_FEATURES', {})
|
||||
return features.get('requires_keypair', False)
|
||||
|
||||
|
||||
def can_set_quotas():
|
||||
features = getattr(settings, 'OPENSTACK_HYPERVISOR_FEATURES', {})
|
||||
return features.get('enable_quotas', True)
|
||||
|
@ -312,9 +312,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
'tenant_quota_usages',),
|
||||
api.cinder: ('tenant_quota_update',),
|
||||
api.nova: ('tenant_quota_update',)})
|
||||
def test_add_project_post(self, neutron=False):
|
||||
def test_add_project_post(self):
|
||||
project = self.tenants.first()
|
||||
quota = self.quotas.first()
|
||||
disabled_quotas = self.disabled_quotas.first()
|
||||
default_role = self.roles.first()
|
||||
default_domain = self._get_default_domain()
|
||||
domain_id = default_domain.id
|
||||
@ -325,9 +326,6 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
# init
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.disabled_quotas.first())
|
||||
if neutron:
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.disabled_quotas.first())
|
||||
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
|
||||
|
||||
api.keystone.get_default_role(IsA(http.HttpRequest)) \
|
||||
@ -364,8 +362,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
group=group_id,
|
||||
project=self.tenant.id)
|
||||
|
||||
nova_updated_quota = dict([(key, quota_data[key]) for key in
|
||||
quotas.NOVA_QUOTA_FIELDS])
|
||||
nova_updated_quota = {key: quota_data[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(disabled_quotas)
|
||||
api.nova.tenant_quota_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**nova_updated_quota)
|
||||
@ -406,7 +406,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
api.neutron.tenant_quota_update(IsA(http.HttpRequest),
|
||||
self.tenant.id,
|
||||
**neutron_updated_quota)
|
||||
self.test_add_project_post(neutron=True)
|
||||
self.test_add_project_post()
|
||||
|
||||
@test.create_stubs({api.keystone: ('user_list',
|
||||
'role_list',
|
||||
@ -531,6 +531,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
def test_add_project_quota_update_error(self):
|
||||
project = self.tenants.first()
|
||||
quota = self.quotas.first()
|
||||
disabled_quotas = self.disabled_quotas.first()
|
||||
default_role = self.roles.first()
|
||||
default_domain = self._get_default_domain()
|
||||
domain_id = default_domain.id
|
||||
@ -579,8 +580,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
group=group_id,
|
||||
project=self.tenant.id)
|
||||
|
||||
nova_updated_quota = dict([(key, quota_data[key]) for key in
|
||||
quotas.NOVA_QUOTA_FIELDS])
|
||||
nova_updated_quota = {key: quota_data[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(disabled_quotas)
|
||||
api.nova.tenant_quota_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**nova_updated_quota) \
|
||||
@ -617,6 +620,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
def test_add_project_user_update_error(self):
|
||||
project = self.tenants.first()
|
||||
quota = self.quotas.first()
|
||||
disabled_quotas = self.disabled_quotas.first()
|
||||
default_role = self.roles.first()
|
||||
default_domain = self._get_default_domain()
|
||||
domain_id = default_domain.id
|
||||
@ -660,8 +664,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
break
|
||||
break
|
||||
|
||||
nova_updated_quota = dict([(key, quota_data[key]) for key in
|
||||
quotas.NOVA_QUOTA_FIELDS])
|
||||
nova_updated_quota = {key: quota_data[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(disabled_quotas)
|
||||
api.nova.tenant_quota_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**nova_updated_quota)
|
||||
@ -961,11 +967,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
quotas: ('get_tenant_quota_data',
|
||||
'get_disabled_quotas',
|
||||
'tenant_quota_usages',)})
|
||||
def test_update_project_save(self, neutron=False):
|
||||
def test_update_project_save(self):
|
||||
keystone_api_version = api.keystone.VERSIONS.active
|
||||
|
||||
project = self.tenants.first()
|
||||
quota = self.quotas.first()
|
||||
disabled_quotas = self.disabled_quotas.first()
|
||||
default_role = self.roles.first()
|
||||
domain_id = project.domain_id
|
||||
users = self._get_all_users(domain_id)
|
||||
@ -983,9 +990,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(self.domain)
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.disabled_quotas.first())
|
||||
if neutron:
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.disabled_quotas.first())
|
||||
quotas.get_tenant_quota_data(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id) \
|
||||
.AndReturn(quota)
|
||||
@ -1047,8 +1051,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
|
||||
.AndReturn(quota_usages)
|
||||
|
||||
nova_updated_quota = dict([(key, updated_quota[key]) for key in
|
||||
quotas.NOVA_QUOTA_FIELDS])
|
||||
nova_updated_quota = {key: updated_quota[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(disabled_quotas)
|
||||
api.nova.tenant_quota_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**nova_updated_quota)
|
||||
@ -1093,7 +1099,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
api.neutron.tenant_quota_update(IsA(http.HttpRequest),
|
||||
self.tenant.id,
|
||||
**neutron_updated_quota)
|
||||
self.test_update_project_save(neutron=True)
|
||||
self.test_update_project_save()
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_get',)})
|
||||
def test_update_project_get_error(self):
|
||||
@ -1256,6 +1262,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
|
||||
project = self.tenants.first()
|
||||
quota = self.quotas.first()
|
||||
disabled_quotas = self.disabled_quotas.first()
|
||||
default_role = self.roles.first()
|
||||
domain_id = project.domain_id
|
||||
users = self._get_all_users(domain_id)
|
||||
@ -1335,8 +1342,10 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest), tenant_id=project.id) \
|
||||
.AndReturn(quota_usages)
|
||||
|
||||
nova_updated_quota = dict([(key, updated_quota[key]) for key in
|
||||
quotas.NOVA_QUOTA_FIELDS])
|
||||
nova_updated_quota = {key: updated_quota[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(disabled_quotas)
|
||||
api.nova.tenant_quota_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**nova_updated_quota) \
|
||||
@ -1458,6 +1467,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
|
||||
api.keystone.user_list(IsA(http.HttpRequest),
|
||||
domain=domain_id).AndReturn(users)
|
||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.disabled_quotas.first())
|
||||
|
||||
self._check_role_list(keystone_api_version, role_assignments, groups,
|
||||
proj_users, roles, workflow_data)
|
||||
|
@ -385,9 +385,9 @@ class UpdateProjectGroups(workflows.UpdateMembersStep):
|
||||
|
||||
class CommonQuotaWorkflow(workflows.Workflow):
|
||||
def _update_project_quota(self, request, data, project_id):
|
||||
# Update the project quota.
|
||||
nova_data = dict(
|
||||
[(key, data[key]) for key in quotas.NOVA_QUOTA_FIELDS])
|
||||
disabled_quotas = quotas.get_disabled_quotas(request)
|
||||
nova_data = {key: data[key] for key in
|
||||
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
|
||||
nova.tenant_quota_update(request, project_id, **nova_data)
|
||||
|
||||
if cinder.is_volume_service_enabled(request):
|
||||
@ -400,7 +400,6 @@ class CommonQuotaWorkflow(workflows.Workflow):
|
||||
if api.base.is_service_enabled(request, 'network') and \
|
||||
api.neutron.is_quotas_extension_supported(request):
|
||||
neutron_data = {}
|
||||
disabled_quotas = quotas.get_disabled_quotas(request)
|
||||
for key in quotas.NEUTRON_QUOTA_FIELDS:
|
||||
if key not in disabled_quotas:
|
||||
neutron_data[key] = data[key]
|
||||
|
@ -251,6 +251,7 @@ OPENSTACK_HYPERVISOR_FEATURES = {
|
||||
'can_set_mount_point': False,
|
||||
'can_set_password': False,
|
||||
'requires_keypair': False,
|
||||
'enable_quotas': True
|
||||
}
|
||||
|
||||
# The OPENSTACK_CINDER_FEATURES settings can be used to enable optional
|
||||
|
@ -403,8 +403,8 @@ def data(TEST):
|
||||
TEST.quotas.add(base.QuotaSet(quota))
|
||||
|
||||
# nova quotas disabled when neutron is enabled
|
||||
disabled_quotas_nova = ['floating_ips', 'fixed_ips',
|
||||
'security_groups', 'security_group_rules']
|
||||
disabled_quotas_nova = {'floating_ips', 'fixed_ips',
|
||||
'security_groups', 'security_group_rules'}
|
||||
TEST.disabled_quotas.add(disabled_quotas_nova)
|
||||
|
||||
# Quota Usages
|
||||
|
@ -20,6 +20,7 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django import http
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mox3.mox import IsA # noqa
|
||||
|
||||
@ -32,17 +33,26 @@ from openstack_dashboard.usage import quotas
|
||||
|
||||
class QuotaTests(test.APITestCase):
|
||||
|
||||
def get_usages(self, with_volume=True):
|
||||
usages = {'injected_file_content_bytes': {'quota': 1},
|
||||
'metadata_items': {'quota': 1},
|
||||
'injected_files': {'quota': 1},
|
||||
'security_groups': {'quota': 10},
|
||||
'security_group_rules': {'quota': 20},
|
||||
'fixed_ips': {'quota': 10},
|
||||
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
|
||||
'floating_ips': {'available': 0, 'used': 2, 'quota': 1},
|
||||
'instances': {'available': 8, 'used': 2, 'quota': 10},
|
||||
'cores': {'available': 8, 'used': 2, 'quota': 10}}
|
||||
def get_usages(self, with_volume=True, nova_quotas_enabled=True):
|
||||
if nova_quotas_enabled:
|
||||
usages = {'injected_file_content_bytes': {'quota': 1},
|
||||
'metadata_items': {'quota': 1},
|
||||
'injected_files': {'quota': 1},
|
||||
'security_groups': {'quota': 10},
|
||||
'security_group_rules': {'quota': 20},
|
||||
'fixed_ips': {'quota': 10},
|
||||
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
|
||||
'floating_ips': {'available': 0, 'used': 2, 'quota': 1},
|
||||
'instances': {'available': 8, 'used': 2, 'quota': 10},
|
||||
'cores': {'available': 8, 'used': 2, 'quota': 10}}
|
||||
else:
|
||||
inf = float('inf')
|
||||
usages = {'security_groups': {'available': inf, 'quota': inf},
|
||||
'ram': {'available': inf, 'used': 1024, 'quota': inf},
|
||||
'floating_ips': {
|
||||
'available': inf, 'used': 2, 'quota': inf},
|
||||
'instances': {'available': inf, 'used': 2, 'quota': inf},
|
||||
'cores': {'available': inf, 'used': 2, 'quota': inf}}
|
||||
if with_volume:
|
||||
usages.update({'volumes': {'available': 0, 'used': 4, 'quota': 1},
|
||||
'snapshots': {'available': 0, 'used': 3,
|
||||
@ -51,6 +61,13 @@ class QuotaTests(test.APITestCase):
|
||||
'quota': 1000}})
|
||||
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}
|
||||
actual_available = {key: value['available'] for key, value in
|
||||
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',),
|
||||
@ -60,13 +77,13 @@ class QuotaTests(test.APITestCase):
|
||||
cinder: ('volume_list', 'volume_snapshot_list',
|
||||
'tenant_quota_get',
|
||||
'is_volume_service_enabled')})
|
||||
def test_tenant_quota_usages(self):
|
||||
def _test_tenant_quota_usages(self, nova_quotas_enabled=True,
|
||||
with_volume=True):
|
||||
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)
|
||||
).AndReturn(True)
|
||||
cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(
|
||||
with_volume)
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest),
|
||||
'network').AndReturn(False)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
@ -81,21 +98,49 @@ class QuotaTests(test.APITestCase):
|
||||
api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts,
|
||||
all_tenants=True) \
|
||||
.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())
|
||||
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())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
quota_usages = quotas.tenant_quota_usages(self.request)
|
||||
expected_output = self.get_usages()
|
||||
expected_output = self.get_usages(
|
||||
nova_quotas_enabled=nova_quotas_enabled, with_volume=with_volume)
|
||||
|
||||
# Compare internal structure of usages to expected.
|
||||
self.assertItemsEqual(expected_output, quota_usages.usages)
|
||||
# Compare available resources
|
||||
self.assertAvailableQuotasEqual(expected_output, quota_usages.usages)
|
||||
|
||||
def test_tenant_quota_usages(self):
|
||||
self._test_tenant_quota_usages()
|
||||
|
||||
@override_settings(OPENSTACK_HYPERVISOR_FEATURES={'enable_quotas': False})
|
||||
def test_tenant_quota_usages_wo_nova_quotas(self):
|
||||
self._test_tenant_quota_usages(nova_quotas_enabled=False,
|
||||
with_volume=False)
|
||||
|
||||
@override_settings(OPENSTACK_HYPERVISOR_FEATURES={'enable_quotas': False})
|
||||
@test.create_stubs({api.base: ('is_service_enabled',),
|
||||
cinder: ('is_volume_service_enabled',)})
|
||||
def test_get_all_disabled_quotas(self):
|
||||
cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(
|
||||
False)
|
||||
api.base.is_service_enabled(IsA(http.HttpRequest),
|
||||
'network').AndReturn(False)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result_quotas = quotas.get_disabled_quotas(self.request)
|
||||
expected_quotas = list(quotas.CINDER_QUOTA_FIELDS) + \
|
||||
list(quotas.NEUTRON_QUOTA_FIELDS) + list(quotas.NOVA_QUOTA_FIELDS)
|
||||
self.assertItemsEqual(result_quotas, expected_quotas)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_list',
|
||||
'flavor_list',
|
||||
|
@ -129,7 +129,8 @@ class QuotaUsage(dict):
|
||||
|
||||
def update_available(self, name):
|
||||
"""Updates the "available" metric for the given quota."""
|
||||
available = self.usages[name]['quota'] - self.usages[name]['used']
|
||||
quota = self.usages.get(name, {}).get('quota', float('inf'))
|
||||
available = quota - self.usages[name]['used']
|
||||
if available < 0:
|
||||
available = 0
|
||||
self.usages[name]['available'] = available
|
||||
@ -148,7 +149,7 @@ def _get_quota_data(request, method_name, disabled_quotas=None,
|
||||
try:
|
||||
quotasets.append(getattr(cinder, method_name)(request, tenant_id))
|
||||
except cinder.cinder_exception.ClientException:
|
||||
disabled_quotas.extend(CINDER_QUOTA_FIELDS)
|
||||
disabled_quotas.update(CINDER_QUOTA_FIELDS)
|
||||
msg = _("Unable to retrieve volume limit information.")
|
||||
exceptions.handle(request, msg)
|
||||
for quota in itertools.chain(*quotasets):
|
||||
@ -228,29 +229,33 @@ def get_tenant_quota_data(request, disabled_quotas=None, tenant_id=None):
|
||||
|
||||
|
||||
def get_disabled_quotas(request):
|
||||
disabled_quotas = []
|
||||
disabled_quotas = set([])
|
||||
|
||||
# Nova
|
||||
if not nova.can_set_quotas():
|
||||
disabled_quotas.update(NOVA_QUOTA_FIELDS)
|
||||
|
||||
# Cinder
|
||||
if not cinder.is_volume_service_enabled(request):
|
||||
disabled_quotas.extend(CINDER_QUOTA_FIELDS)
|
||||
disabled_quotas.update(CINDER_QUOTA_FIELDS)
|
||||
|
||||
# Neutron
|
||||
if not base.is_service_enabled(request, 'network'):
|
||||
disabled_quotas.extend(NEUTRON_QUOTA_FIELDS)
|
||||
disabled_quotas.update(NEUTRON_QUOTA_FIELDS)
|
||||
else:
|
||||
# Remove the nova network quotas
|
||||
disabled_quotas.extend(['floating_ips', 'fixed_ips'])
|
||||
disabled_quotas.update(['floating_ips', 'fixed_ips'])
|
||||
|
||||
if neutron.is_extension_supported(request, 'security-group'):
|
||||
# If Neutron security group is supported, disable Nova quotas
|
||||
disabled_quotas.extend(['security_groups', 'security_group_rules'])
|
||||
disabled_quotas.update(['security_groups', 'security_group_rules'])
|
||||
else:
|
||||
# If Nova security group is used, disable Neutron quotas
|
||||
disabled_quotas.extend(['security_group', 'security_group_rule'])
|
||||
disabled_quotas.update(['security_group', 'security_group_rule'])
|
||||
|
||||
try:
|
||||
if not neutron.is_quotas_extension_supported(request):
|
||||
disabled_quotas.extend(NEUTRON_QUOTA_FIELDS)
|
||||
disabled_quotas.update(NEUTRON_QUOTA_FIELDS)
|
||||
except Exception:
|
||||
LOG.exception("There was an error checking if the Neutron "
|
||||
"quotas extension is enabled.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user