Join quota exception family trees

For some reason, we have two lineages of quota-related exceptions in
Nova. We have QuotaError (which sounds like an actual error), from
which all of our case-specific "over quota" exceptions inhert, such
as KeypairLimitExceeded, etc. In contrast, we have OverQuota which
lives outside that hierarchy and is unrelated. In a number of places,
we raise one and translate to the other, or raise the generic
QuotaError to signal an overquota situation, instead of OverQuota.
This leads to places where we have to catch both, signaling the same
over quota situation, but looking like there could be two different
causes (i.e. an error and being over quota).

This joins the two cases, by putting OverQuota at the top of the
hierarchy of specific exceptions and removing QuotaError. The latter
was only used in a few situations, so this isn't actually much change.
Cleaning this up will help with the unified limits work, reducing the
number of potential exceptions that mean the same thing.

Related to blueprint bp/unified-limits-nova

Change-Id: I17a3e20b8be98f9fb1a04b91fcf1237d67165871
This commit is contained in:
Dan Smith 2022-02-07 12:30:46 -08:00
parent b6fe7521af
commit 72058b7a40
9 changed files with 31 additions and 39 deletions

View File

@ -40,7 +40,7 @@ class DeferredDeleteController(wsgi.Controller):
target={'project_id': instance.project_id}) target={'project_id': instance.project_id})
try: try:
self.compute_api.restore(context, instance) self.compute_api.restore(context, instance)
except exception.QuotaError as error: except exception.OverQuota as error:
raise webob.exc.HTTPForbidden(explanation=error.format_message()) raise webob.exc.HTTPForbidden(explanation=error.format_message())
except exception.InstanceInvalidState as state_error: except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error, common.raise_http_conflict_for_instance_invalid_state(state_error,

View File

@ -57,7 +57,7 @@ class MigrateServerController(wsgi.Controller):
try: try:
self.compute_api.resize(req.environ['nova.context'], instance, self.compute_api.resize(req.environ['nova.context'], instance,
host_name=host_name) host_name=host_name)
except (exception.TooManyInstances, exception.QuotaError) as e: except exception.OverQuota as e:
raise exc.HTTPForbidden(explanation=e.format_message()) raise exc.HTTPForbidden(explanation=e.format_message())
except ( except (
exception.InstanceIsLocked, exception.InstanceIsLocked,

View File

@ -114,7 +114,7 @@ class ServerMetadataController(wsgi.Controller):
server, server,
metadata, metadata,
delete) delete)
except exception.QuotaError as error: except exception.OverQuota as error:
raise exc.HTTPForbidden(explanation=error.format_message()) raise exc.HTTPForbidden(explanation=error.format_message())
except exception.InstanceIsLocked as e: except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message()) raise exc.HTTPConflict(explanation=e.format_message())

View File

@ -797,8 +797,7 @@ class ServersController(wsgi.Controller):
supports_multiattach=supports_multiattach, supports_multiattach=supports_multiattach,
supports_port_resource_request=supports_port_resource_request, supports_port_resource_request=supports_port_resource_request,
**create_kwargs) **create_kwargs)
except (exception.QuotaError, except exception.OverQuota as error:
exception.PortLimitExceeded) as error:
raise exc.HTTPForbidden( raise exc.HTTPForbidden(
explanation=error.format_message()) explanation=error.format_message())
except exception.ImageNotFound: except exception.ImageNotFound:
@ -1053,7 +1052,7 @@ class ServersController(wsgi.Controller):
try: try:
self.compute_api.resize(context, instance, flavor_id, self.compute_api.resize(context, instance, flavor_id,
auto_disk_config=auto_disk_config) auto_disk_config=auto_disk_config)
except exception.QuotaError as error: except exception.OverQuota as error:
raise exc.HTTPForbidden( raise exc.HTTPForbidden(
explanation=error.format_message()) explanation=error.format_message())
except ( except (
@ -1237,7 +1236,7 @@ class ServersController(wsgi.Controller):
except exception.KeypairNotFound: except exception.KeypairNotFound:
msg = _("Invalid key_name provided.") msg = _("Invalid key_name provided.")
raise exc.HTTPBadRequest(explanation=msg) raise exc.HTTPBadRequest(explanation=msg)
except exception.QuotaError as error: except exception.OverQuota as error:
raise exc.HTTPForbidden(explanation=error.format_message()) raise exc.HTTPForbidden(explanation=error.format_message())
except (exception.AutoDiskConfigDisabledByImage, except (exception.AutoDiskConfigDisabledByImage,
exception.CertificateValidationFailed, exception.CertificateValidationFailed,

View File

@ -398,7 +398,7 @@ class API:
def _check_injected_file_quota(self, context, injected_files): def _check_injected_file_quota(self, context, injected_files):
"""Enforce quota limits on injected files. """Enforce quota limits on injected files.
Raises a QuotaError if any limit is exceeded. Raises a OverQuota if any limit is exceeded.
""" """
if not injected_files: if not injected_files:
return return
@ -1455,7 +1455,7 @@ class API:
except exception.OverQuota: except exception.OverQuota:
msg = _("Quota exceeded, too many servers in " msg = _("Quota exceeded, too many servers in "
"group") "group")
raise exception.QuotaError(msg) raise exception.OverQuota(msg)
members = objects.InstanceGroup.add_members( members = objects.InstanceGroup.add_members(
context, instance_group.uuid, [instance.uuid]) context, instance_group.uuid, [instance.uuid])
@ -1475,7 +1475,7 @@ class API:
context, instance_group.id, [instance.uuid]) context, instance_group.id, [instance.uuid])
msg = _("Quota exceeded, too many servers in " msg = _("Quota exceeded, too many servers in "
"group") "group")
raise exception.QuotaError(msg) raise exception.OverQuota(msg)
# list of members added to servers group in this iteration # list of members added to servers group in this iteration
# is needed to check quota of server group during add next # is needed to check quota of server group during add next
# instance # instance

View File

@ -993,10 +993,6 @@ class QuotaClassExists(NovaException):
msg_fmt = _("Quota class %(class_name)s exists for resource %(resource)s") msg_fmt = _("Quota class %(class_name)s exists for resource %(resource)s")
class OverQuota(NovaException):
msg_fmt = _("Quota exceeded for resources: %(overs)s")
class SecurityGroupNotFound(NotFound): class SecurityGroupNotFound(NotFound):
msg_fmt = _("Security group %(security_group_id)s not found.") msg_fmt = _("Security group %(security_group_id)s not found.")
@ -1233,29 +1229,26 @@ class MaxRetriesExceeded(NoValidHost):
msg_fmt = _("Exceeded maximum number of retries. %(reason)s") msg_fmt = _("Exceeded maximum number of retries. %(reason)s")
class QuotaError(NovaException): class OverQuota(NovaException):
msg_fmt = _("Quota exceeded: code=%(code)s") msg_fmt = _("Quota exceeded for resources: %(overs)s")
# NOTE(cyeoh): 413 should only be used for the ec2 API
# The error status code for out of quota for the nova api should be
# 403 Forbidden.
code = 413 code = 413
safe = True safe = True
class TooManyInstances(QuotaError): class TooManyInstances(OverQuota):
msg_fmt = _("Quota exceeded for %(overs)s: Requested %(req)s," msg_fmt = _("Quota exceeded for %(overs)s: Requested %(req)s,"
" but already used %(used)s of %(allowed)s %(overs)s") " but already used %(used)s of %(allowed)s %(overs)s")
class FloatingIpLimitExceeded(QuotaError): class FloatingIpLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of floating IPs exceeded") msg_fmt = _("Maximum number of floating IPs exceeded")
class MetadataLimitExceeded(QuotaError): class MetadataLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of metadata items exceeds %(allowed)d") msg_fmt = _("Maximum number of metadata items exceeds %(allowed)d")
class OnsetFileLimitExceeded(QuotaError): class OnsetFileLimitExceeded(OverQuota):
msg_fmt = _("Personality file limit exceeded") msg_fmt = _("Personality file limit exceeded")
@ -1267,15 +1260,15 @@ class OnsetFileContentLimitExceeded(OnsetFileLimitExceeded):
msg_fmt = _("Personality file content exceeds maximum %(allowed)s") msg_fmt = _("Personality file content exceeds maximum %(allowed)s")
class KeypairLimitExceeded(QuotaError): class KeypairLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of key pairs exceeded") msg_fmt = _("Maximum number of key pairs exceeded")
class SecurityGroupLimitExceeded(QuotaError): class SecurityGroupLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of security groups or rules exceeded") msg_fmt = _("Maximum number of security groups or rules exceeded")
class PortLimitExceeded(QuotaError): class PortLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of ports exceeded") msg_fmt = _("Maximum number of ports exceeded")

View File

@ -143,7 +143,7 @@ class APITest(test.NoDBTestCase):
self.assertEqual(resp.headers[key], str(value)) self.assertEqual(resp.headers[key], str(value))
def test_quota_error_mapping(self): def test_quota_error_mapping(self):
self._do_test_exception_mapping(exception.QuotaError, 'too many used') self._do_test_exception_mapping(exception.OverQuota, 'too many used')
def test_non_nova_notfound_exception_mapping(self): def test_non_nova_notfound_exception_mapping(self):
class ExceptionWithCode(Exception): class ExceptionWithCode(Exception):

View File

@ -8856,7 +8856,7 @@ class ComputeAPITestCase(BaseTestCase):
group.create() group.create()
get_group_mock.return_value = group get_group_mock.return_value = group
self.assertRaises(exception.QuotaError, self.compute_api.create, self.assertRaises(exception.OverQuota, self.compute_api.create,
self.context, self.default_flavor, self.fake_image['id'], self.context, self.default_flavor, self.fake_image['id'],
scheduler_hints={'group': group.uuid}, scheduler_hints={'group': group.uuid},
check_server_group_quota=True) check_server_group_quota=True)

View File

@ -109,7 +109,7 @@ class QuotaIntegrationTestCase(test.TestCase):
self.compute_api.create( self.compute_api.create(
self.context, min_count=1, max_count=1, self.context, min_count=1, max_count=1,
flavor=self.flavor, image_href=image_uuid) flavor=self.flavor, image_href=image_uuid)
except exception.QuotaError as e: except exception.OverQuota as e:
expected_kwargs = {'code': 413, expected_kwargs = {'code': 413,
'req': '1, 1', 'req': '1, 1',
'used': '8, 2', 'used': '8, 2',
@ -117,7 +117,7 @@ class QuotaIntegrationTestCase(test.TestCase):
'overs': 'cores, instances'} 'overs': 'cores, instances'}
self.assertEqual(expected_kwargs, e.kwargs) self.assertEqual(expected_kwargs, e.kwargs)
else: else:
self.fail('Expected QuotaError exception') self.fail('Expected OverQuota exception')
def test_too_many_cores(self): def test_too_many_cores(self):
self._create_instance() self._create_instance()
@ -126,7 +126,7 @@ class QuotaIntegrationTestCase(test.TestCase):
self.compute_api.create( self.compute_api.create(
self.context, min_count=1, max_count=1, flavor=self.flavor, self.context, min_count=1, max_count=1, flavor=self.flavor,
image_href=image_uuid) image_href=image_uuid)
except exception.QuotaError as e: except exception.OverQuota as e:
expected_kwargs = {'code': 413, expected_kwargs = {'code': 413,
'req': '1', 'req': '1',
'used': '4', 'used': '4',
@ -134,7 +134,7 @@ class QuotaIntegrationTestCase(test.TestCase):
'overs': 'cores'} 'overs': 'cores'}
self.assertEqual(expected_kwargs, e.kwargs) self.assertEqual(expected_kwargs, e.kwargs)
else: else:
self.fail('Expected QuotaError exception') self.fail('Expected OverQuota exception')
def test_many_cores_with_unlimited_quota(self): def test_many_cores_with_unlimited_quota(self):
# Setting cores quota to unlimited: # Setting cores quota to unlimited:
@ -150,7 +150,7 @@ class QuotaIntegrationTestCase(test.TestCase):
metadata['key%s' % i] = 'value%s' % i metadata['key%s' % i] = 'value%s' % i
image_uuid = 'cedef40a-ed67-4d10-800e-17455edce175' image_uuid = 'cedef40a-ed67-4d10-800e-17455edce175'
self.assertRaises( self.assertRaises(
exception.QuotaError, self.compute_api.create, exception.OverQuota, self.compute_api.create,
self.context, min_count=1, max_count=1, flavor=self.flavor, self.context, min_count=1, max_count=1, flavor=self.flavor,
image_href=image_uuid, metadata=metadata) image_href=image_uuid, metadata=metadata)
@ -170,39 +170,39 @@ class QuotaIntegrationTestCase(test.TestCase):
files = [] files = []
for i in range(CONF.quota.injected_files): for i in range(CONF.quota.injected_files):
files.append(('/my/path%d' % i, 'config = test\n')) files.append(('/my/path%d' % i, 'config = test\n'))
self._create_with_injected_files(files) # no QuotaError self._create_with_injected_files(files) # no OverQuota
def test_too_many_injected_files(self): def test_too_many_injected_files(self):
files = [] files = []
for i in range(CONF.quota.injected_files + 1): for i in range(CONF.quota.injected_files + 1):
files.append(('/my/path%d' % i, 'my\ncontent%d\n' % i)) files.append(('/my/path%d' % i, 'my\ncontent%d\n' % i))
self.assertRaises(exception.QuotaError, self.assertRaises(exception.OverQuota,
self._create_with_injected_files, files) self._create_with_injected_files, files)
def test_max_injected_file_content_bytes(self): def test_max_injected_file_content_bytes(self):
max = CONF.quota.injected_file_content_bytes max = CONF.quota.injected_file_content_bytes
content = ''.join(['a' for i in range(max)]) content = ''.join(['a' for i in range(max)])
files = [('/test/path', content)] files = [('/test/path', content)]
self._create_with_injected_files(files) # no QuotaError self._create_with_injected_files(files) # no OverQuota
def test_too_many_injected_file_content_bytes(self): def test_too_many_injected_file_content_bytes(self):
max = CONF.quota.injected_file_content_bytes max = CONF.quota.injected_file_content_bytes
content = ''.join(['a' for i in range(max + 1)]) content = ''.join(['a' for i in range(max + 1)])
files = [('/test/path', content)] files = [('/test/path', content)]
self.assertRaises(exception.QuotaError, self.assertRaises(exception.OverQuota,
self._create_with_injected_files, files) self._create_with_injected_files, files)
def test_max_injected_file_path_bytes(self): def test_max_injected_file_path_bytes(self):
max = CONF.quota.injected_file_path_length max = CONF.quota.injected_file_path_length
path = ''.join(['a' for i in range(max)]) path = ''.join(['a' for i in range(max)])
files = [(path, 'config = quotatest')] files = [(path, 'config = quotatest')]
self._create_with_injected_files(files) # no QuotaError self._create_with_injected_files(files) # no OverQuota
def test_too_many_injected_file_path_bytes(self): def test_too_many_injected_file_path_bytes(self):
max = CONF.quota.injected_file_path_length max = CONF.quota.injected_file_path_length
path = ''.join(['a' for i in range(max + 1)]) path = ''.join(['a' for i in range(max + 1)])
files = [(path, 'config = quotatest')] files = [(path, 'config = quotatest')]
self.assertRaises(exception.QuotaError, self.assertRaises(exception.OverQuota,
self._create_with_injected_files, files) self._create_with_injected_files, files)