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})
try:
self.compute_api.restore(context, instance)
except exception.QuotaError as error:
except exception.OverQuota as error:
raise webob.exc.HTTPForbidden(explanation=error.format_message())
except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error,

View File

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

View File

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

View File

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

View File

@ -398,7 +398,7 @@ class API:
def _check_injected_file_quota(self, context, 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:
return
@ -1455,7 +1455,7 @@ class API:
except exception.OverQuota:
msg = _("Quota exceeded, too many servers in "
"group")
raise exception.QuotaError(msg)
raise exception.OverQuota(msg)
members = objects.InstanceGroup.add_members(
context, instance_group.uuid, [instance.uuid])
@ -1475,7 +1475,7 @@ class API:
context, instance_group.id, [instance.uuid])
msg = _("Quota exceeded, too many servers in "
"group")
raise exception.QuotaError(msg)
raise exception.OverQuota(msg)
# list of members added to servers group in this iteration
# is needed to check quota of server group during add next
# instance

View File

@ -993,10 +993,6 @@ class QuotaClassExists(NovaException):
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):
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")
class QuotaError(NovaException):
msg_fmt = _("Quota exceeded: code=%(code)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.
class OverQuota(NovaException):
msg_fmt = _("Quota exceeded for resources: %(overs)s")
code = 413
safe = True
class TooManyInstances(QuotaError):
class TooManyInstances(OverQuota):
msg_fmt = _("Quota exceeded for %(overs)s: Requested %(req)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")
class MetadataLimitExceeded(QuotaError):
class MetadataLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of metadata items exceeds %(allowed)d")
class OnsetFileLimitExceeded(QuotaError):
class OnsetFileLimitExceeded(OverQuota):
msg_fmt = _("Personality file limit exceeded")
@ -1267,15 +1260,15 @@ class OnsetFileContentLimitExceeded(OnsetFileLimitExceeded):
msg_fmt = _("Personality file content exceeds maximum %(allowed)s")
class KeypairLimitExceeded(QuotaError):
class KeypairLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of key pairs exceeded")
class SecurityGroupLimitExceeded(QuotaError):
class SecurityGroupLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of security groups or rules exceeded")
class PortLimitExceeded(QuotaError):
class PortLimitExceeded(OverQuota):
msg_fmt = _("Maximum number of ports exceeded")

View File

@ -143,7 +143,7 @@ class APITest(test.NoDBTestCase):
self.assertEqual(resp.headers[key], str(value))
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):
class ExceptionWithCode(Exception):

View File

@ -8856,7 +8856,7 @@ class ComputeAPITestCase(BaseTestCase):
group.create()
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'],
scheduler_hints={'group': group.uuid},
check_server_group_quota=True)

View File

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