Pay attention to Nova disabled quotas defined in a config file

Since Nova doesn't currently provide an API call to indicate whether
its quotas are disabled, we could use a parameter named
'enable_quotas' within 'OPENSTACK_HYPERVISOR_FEATURES' setting for
this purpose. This allows to avoid errors while trying to update
quotas which are disabled on service side. Also make disabled_quotas
collection to be a set instead of a list - this removes duplicate
fields that appear due to some possible quota overlaps between Nova
and Neutron.

Also, since we dropped out python2.6 support fancy set literals and
dict comprehensions can be used.

Co-Authored-By: Paul Karikh <pkarikh@mirantis.com>
Closes-Bug: #1286099
Change-Id: I10923f147e4c323aba8bbcc130d2016ad6725e86
This commit is contained in:
Timur Sufiev 2015-08-21 13:22:03 +03:00
parent 9e4e8e00d5
commit baca29144b
8 changed files with 130 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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