Reorganize hierarchy of exception classes

Separate ancestors of ec2api internal and aws compliant exceptions.
EC2APIException - base exception class for ec2api with formatting
support
EC2MetadataException - still covers internal exception of ec2api
metadata service
EC2Exception - still covers AWS compliant exceptions
EC2xxxException - derive EC2Exception and are intended to group
exceptions by kind

Also remove unused attributes and code from base exception class.
Fix string constant style to be 'xxx' wherever it's possible.

Change-Id: I398c1e8e1816e0d4073fa5d3fdf0c0a939470399
This commit is contained in:
Feodor Tersin 2015-03-20 17:24:48 +03:00
parent 070fe23aaf
commit 99e124814c
7 changed files with 286 additions and 272 deletions

View File

@ -756,7 +756,8 @@ def _s3_test_for_malicious_tarball(path, filename):
for n in tar_file.getnames():
if not os.path.abspath(os.path.join(path, n)).startswith(path):
tar_file.close()
raise exception.Invalid(_('Unsafe filenames in image'))
# TODO(ft): figure out actual AWS exception
raise exception.EC2InvalidException(_('Unsafe filenames in image'))
tar_file.close()

View File

@ -596,7 +596,7 @@ def _parse_image_parameters(context, image_id, kernel_id, ramdisk_id):
if _cloud_get_image_state(os_image) != 'available':
# TODO(ft): Change the message with the real AWS message
msg = _('Image must be available')
raise exception.ImageNotActive(message=msg)
raise exception.InvalidAMIIDUnavailable(message=msg)
return os_image, os_kernel_id, os_ramdisk_id

View File

@ -178,9 +178,9 @@ def get_os_admin_context():
def require_context(ctxt):
"""Raise exception.Forbidden()
"""Raise exception.AuthFailure()
if context is not a user or an admin context.
"""
if not ctxt.is_admin and not is_user_context(ctxt):
raise exception.Forbidden()
raise exception.AuthFailure()

View File

@ -40,40 +40,18 @@ CONF = cfg.CONF
CONF.register_opts(exc_log_opts)
class EC2MetadataException(Exception):
pass
class EC2MetadataNotFound(EC2MetadataException):
pass
class EC2MetadataInvalidAddress(EC2MetadataException):
pass
class EC2Exception(Exception):
"""Base EC2 Exception
class EC2APIException(Exception):
"""Base EC2 API Exception
To correctly use this class, inherit from it and define
a 'msg_fmt' property. That msg_fmt will get printf'd
with the keyword arguments provided to the constructor.
"""
msg_fmt = _("An unknown exception occurred.")
code = 400
headers = {}
safe = False
msg_fmt = _('An unknown exception occurred.')
def __init__(self, message=None, **kwargs):
self.kwargs = kwargs
if 'code' not in self.kwargs:
try:
self.kwargs['code'] = self.code
except AttributeError:
pass
if not message:
try:
message = self.msg_fmt % kwargs
@ -84,7 +62,7 @@ class EC2Exception(Exception):
LOG.exception(_('Exception in string format operation for '
'%s exception'), self.__class__.__name__)
for name, value in kwargs.iteritems():
LOG.error("%s: %s" % (name, value))
LOG.error('%s: %s' % (name, value))
if CONF.fatal_exception_format_errors:
raise exc_info[0], exc_info[1], exc_info[2]
@ -100,311 +78,345 @@ class EC2Exception(Exception):
else:
message = self.msg_fmt
super(EC2Exception, self).__init__(message)
super(EC2APIException, self).__init__(message)
def format_message(self):
# NOTE(mrodden): use the first argument to the python Exception object
# which should be our full EC2Exception message, (see __init__)
# which should be our full EC2APIException message, (see __init__)
return self.args[0]
# Internal ec2api exceptions
class EC2APIConfigNotFound(EC2APIException):
msg_fmt = _("Could not find config at %(path)s")
class EC2APIPasteAppNotFound(EC2APIException):
msg_fmt = _("Could not load paste app '%(name)s' from %(path)s")
# Internal ec2api metadata exceptions
class EC2MetadataException(EC2APIException):
pass
class EC2MetadataNotFound(EC2MetadataException):
pass
class EC2MetadataInvalidAddress(EC2MetadataException):
pass
# Intermediate exception classes to organize AWS exception hierarchy
class EC2Exception(EC2APIException):
"""Base EC2 compliant exception
To correctly use this class, inherit from it and define
a 'ec2_code' property if a new class name doesn't coincide with
AWS Error Code.
"""
code = 400
class EC2InvalidException(EC2Exception):
pass
class EC2IncorrectStateException(EC2Exception):
pass
class EC2DuplicateException(EC2InvalidException):
pass
class EC2InUseException(EC2InvalidException):
pass
class EC2NotFoundException(EC2InvalidException):
pass
class EC2OverlimitException(EC2Exception):
pass
# AWS compliant exceptions
class Unsupported(EC2Exception):
msg_fmt = _("The specified request is unsupported. %(reason)s")
class Overlimit(EC2Exception):
msg_fmt = _("Limit exceeded.")
class UnsupportedOperation(EC2Exception):
msg_fmt = _('The specified request includes an unsupported operation.')
class Invalid(EC2Exception):
msg_fmt = _("Unacceptable parameters.")
class OperationNotPermitted(EC2Exception):
msg_fmt = _('The specified operation is not allowed.')
class InvalidRequest(Invalid):
msg_fmt = _("The request is invalid.")
class InvalidRequest(EC2InvalidException):
msg_fmt = _('The request is invalid.')
class InvalidAttribute(Invalid):
class InvalidAttribute(EC2InvalidException):
msg_fmt = _("Attribute not supported: %(attr)s")
class InvalidID(Invalid):
class InvalidID(EC2InvalidException):
msg_fmt = _("The ID '%(id)s' is not valid")
class InvalidInput(Invalid):
class InvalidInput(EC2InvalidException):
msg_fmt = _("Invalid input received: %(reason)s")
class ConfigNotFound(EC2Exception):
msg_fmt = _("Could not find config at %(path)s")
class AuthFailure(EC2InvalidException):
msg_fmt = _('Not authorized.')
class PasteAppNotFound(EC2Exception):
msg_fmt = _("Could not load paste app '%(name)s' from %(path)s")
class Forbidden(EC2Exception):
ec2_code = 'AuthFailure'
msg_fmt = _("Not authorized.")
code = 403
class AuthFailure(Invalid):
pass
class ValidationError(Invalid):
class ValidationError(EC2InvalidException):
msg_fmt = _("The input fails to satisfy the constraints "
"specified by an AWS service: '%(reason)s'")
class EC2NotFound(EC2Exception):
msg_fmt = _("Resource could not be found.")
class InvalidInstanceIDNotFound(EC2NotFound):
ec2_code = 'InvalidInstanceID.NotFound'
msg_fmt = _("The instance ID '%(id)s' does not exist")
class InvalidVpcIDNotFound(EC2NotFound):
ec2_code = 'InvalidVpcID.NotFound'
msg_fmt = _("The vpc ID '%(id)s' does not exist")
class InvalidInternetGatewayIDNotFound(EC2NotFound):
ec2_code = 'InvalidInternetGatewayID.NotFound'
msg_fmt = _("The internetGateway ID '%(id)s' does not exist")
class InvalidSubnetIDNotFound(EC2NotFound):
ec2_code = 'InvalidSubnetID.NotFound'
msg_fmt = _("The subnet ID '%(id)s' does not exist")
class InvalidNetworkInterfaceIDNotFound(EC2NotFound):
ec2_code = 'InvalidNetworkInterfaceID.NotFound'
msg_fmt = _("Network interface %(id)s could not "
"be found.")
class InvalidAttachmentIDNotFound(EC2NotFound):
ec2_code = 'InvalidAttachmentID.NotFound'
msg_fmt = _("Attachment %(id)s could not "
"be found.")
class InvalidDhcpOptionsIDNotFound(EC2NotFound):
ec2_code = 'InvalidDhcpOptionsID.NotFound'
msg_fmt = _("The dhcp options ID '%(id)s' does not exist")
class InvalidAllocationIDNotFound(EC2NotFound):
ec2_code = 'InvalidAllocationID.NotFound'
msg_fmt = _("The allocation ID '%(id)s' does not exist")
class InvalidAssociationIDNotFound(EC2NotFound):
ec2_code = 'InvalidAssociationID.NotFound'
msg_fmt = _("The association ID '%(id)s' does not exist")
class InvalidRouteTableIDNotFound(EC2NotFound):
ec2_code = 'InvalidRouteTableID.NotFound'
msg_fmt = _("The routeTable ID '%(id)s' does not exist")
class InvalidRouteNotFound(EC2NotFound):
ec2_code = 'InvalidRoute.NotFound'
msg_fmt = _('No route with destination-cidr-block '
'%(destination_cidr_block)s in route table %(route_table_id)s')
class InvalidSecurityGroupIDNotFound(EC2NotFound):
ec2_code = 'InvalidSecurityGroupID.NotFound'
msg_fmt = _("The securityGroup ID '%(id)s' does not exist")
class InvalidGroupNotFound(EC2NotFound):
ec2_code = 'InvalidGroup.NotFound'
msg_fmg = _("The security group ID '%(id)s' does not exist")
class InvalidPermissionNotFound(EC2NotFound):
ec2_code = 'InvalidPermission.NotFound'
msg_fmg = _("The specified permission does not exist")
class InvalidVolumeNotFound(EC2NotFound):
ec2_code = 'InvalidVolume.NotFound'
msg_fmt = _("The volume '%(id)s' does not exist.")
class InvalidSnapshotNotFound(EC2NotFound):
ec2_code = 'InvalidSnapshot.NotFound'
msg_fmt = _("Snapshot %(id)s could not be found.")
class InvalidAMIIDNotFound(EC2NotFound):
ec2_code = 'InvalidAMIID.NotFound'
msg_fmt = _("The image id '[%(id)s]' does not exist")
class InvalidKeypairNotFound(EC2NotFound):
ec2_code = 'InvalidKeyPair.NotFound'
msg_fmt = _("Keypair %(id)s is not found")
class InvalidAvailabilityZoneNotFound(EC2NotFound):
ec2_code = 'InvalidAvailabilityZone.NotFound'
msg_fmt = _("Availability zone %(id)s not found")
class IncorrectState(EC2Exception):
ec2_code = 'IncorrectState'
msg_fmt = _("The resource is in incorrect state for the request - reason: "
"'%(reason)s'")
class IncorrectInstanceState(IncorrectState):
ec2_code = 'IncorrectInstanceState'
msg_fmt = _("The instance '%(instance_id)s' is not in a state from which "
"the requested operation can be performed.")
class InvalidVpcRange(Invalid):
ec2_code = 'InvalidVpc.Range'
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
class InvalidSubnetRange(Invalid):
ec2_code = 'InvalidSubnet.Range'
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
class InvalidSubnetConflict(Invalid):
ec2_code = 'InvalidSubnet.Conflict'
msg_fmt = _("The CIDR '%(cidr_block)s' conflicts with another subnet")
class MissingParameter(Invalid):
class MissingParameter(EC2InvalidException):
msg_fmt = _("The required parameter '%(param)s' is missing")
class InvalidParameter(Invalid):
class InvalidParameter(EC2InvalidException):
msg_fmt = _("The property '%(name)s' is not valid")
class InvalidParameterValue(Invalid):
class InvalidParameterValue(EC2InvalidException):
msg_fmt = _("Value (%(value)s) for parameter %(parameter)s is invalid. "
"%(reason)s")
class InvalidParameterCombination(Invalid):
pass
class InvalidFilter(EC2InvalidException):
msg_fmt = _('The filter is invalid.')
class UnsupportedOperation(Invalid):
pass
class InvalidParameterCombination(EC2InvalidException):
msg_fmt = _('The combination of parameters in incorrect')
class OperationNotPermitted(Invalid):
pass
class InvalidVpcRange(EC2InvalidException):
ec2_code = 'InvalidVpc.Range'
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
class ResourceAlreadyAssociated(Invalid):
ec2_code = 'Resource.AlreadyAssociated'
class InvalidSubnetRange(EC2InvalidException):
ec2_code = 'InvalidSubnet.Range'
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
class GatewayNotAttached(Invalid):
ec2_code = 'Gateway.NotAttached'
msg_fmt = _("resource %(igw_id)s is not attached to network %(vpc_id)s")
class InvalidSubnetConflict(EC2InvalidException):
ec2_code = 'InvalidSubnet.Conflict'
msg_fmt = _("The CIDR '%(cidr_block)s' conflicts with another subnet")
class DependencyViolation(Invalid):
msg_fmt = _('Object %(obj1_id)s has dependent resource %(obj2_id)s')
class InvalidNetworkInterfaceInUse(Invalid):
ec2_code = 'InvalidNetworkInterface.InUse'
msg_fmt = _('Interface: %(interface_ids)s in use.')
class InvalidInstanceId(Invalid):
class InvalidInstanceId(EC2InvalidException):
ec2_code = 'InvalidInstanceID'
msg_fmt = _("There are multiple interfaces attached to instance "
"'%(instance_id)s'. Please specify an interface ID for "
"the operation instead.")
class InvalidIPAddressInUse(Invalid):
ec2_code = 'InvalidIPAddress.InUse'
msg_fmt = _('Address %(ip_address)s is in use.')
class InvalidAddressNotFound(Invalid):
ec2_code = 'InvalidAddress.NotFound'
msg_fmt = _('The specified elastic IP address %(ip)s cannot be found.')
class RouteAlreadyExists(Invalid):
msg_fmt = _('The route identified by %(destination_cidr_block)s '
'already exists.')
class VpcLimitExceeded(Overlimit):
msg_fmt = _('The maximum number of VPCs has been reached.')
class SubnetLimitExceeded(Overlimit):
msg_fmt = _('You have reached the limit on the number of subnets that you '
'can create')
class NetworkInterfaceLimitExceeded(Overlimit):
msg_fmt = _('You have reached the limit of network interfaces for subnet'
'%(subnet_id)s.')
class ResourceLimitExceeded(Overlimit):
msg_fmt = _('You have reached the limit of %(resource)s')
class SecurityGroupLimitExceeded(Overlimit):
msg_fmt = _('You have reached the limit of security groups')
class AddressLimitExceeded(Overlimit):
msg_fmt = _('The maximum number of addresses has been reached.')
class ImageNotActive(Invalid):
ec2_code = 'InvalidAMIID.Unavailable'
# TODO(ft): Change the message with the real AWS message
msg_fmt = _("Image %(image_id)s is not active.")
class InvalidSnapshotIDMalformed(Invalid):
class InvalidSnapshotIDMalformed(EC2InvalidException):
ec2_code = 'InvalidSnapshotID.Malformed'
# TODO(ft): Change the message with the real AWS message
msg_fmg = _('The snapshot %(id)s ID is not valid')
class InvalidKeyPairDuplicate(Invalid):
class IncorrectState(EC2IncorrectStateException):
msg_fmt = _("The resource is in incorrect state for the request - reason: "
"'%(reason)s'")
class DependencyViolation(EC2IncorrectStateException):
msg_fmt = _('Object %(obj1_id)s has dependent resource %(obj2_id)s')
class ResourceAlreadyAssociated(EC2IncorrectStateException):
ec2_code = 'Resource.AlreadyAssociated'
class GatewayNotAttached(EC2IncorrectStateException):
ec2_code = 'Gateway.NotAttached'
msg_fmt = _("resource %(igw_id)s is not attached to network %(vpc_id)s")
class IncorrectInstanceState(EC2IncorrectStateException):
msg_fmt = _("The instance '%(instance_id)s' is not in a state from which "
"the requested operation can be performed.")
class InvalidAMIIDUnavailable(EC2IncorrectStateException):
ec2_code = 'InvalidAMIID.Unavailable'
# TODO(ft): Change the message with the real AWS message
msg_fmt = _("Image %(image_id)s is not active.")
class InvalidNetworkInterfaceInUse(EC2InUseException):
ec2_code = 'InvalidNetworkInterface.InUse'
msg_fmt = _('Interface: %(interface_ids)s in use.')
class InvalidIPAddressInUse(EC2InUseException):
ec2_code = 'InvalidIPAddress.InUse'
msg_fmt = _('Address %(ip_address)s is in use.')
class InvalidKeyPairDuplicate(EC2DuplicateException):
ec2_code = 'InvalidKeyPair.Duplicate'
msg_fmt = _("Key pair '%(key_name)s' already exists.")
class InvalidPermissionDuplicate(Invalid):
class InvalidPermissionDuplicate(EC2DuplicateException):
ec2_code = 'InvalidPermission.Duplicate'
msg_fmt = _("The specified rule already exists for that security group.")
msg_fmt = _('The specified rule already exists for that security group.')
class InvalidFilter(Invalid):
msg_fmt = _("The filter is invalid.")
class RouteAlreadyExists(EC2DuplicateException):
msg_fmt = _('The route identified by %(destination_cidr_block)s '
'already exists.')
class RulesPerSecurityGroupLimitExceeded(Overlimit):
class InvalidVpcIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidVpcID.NotFound'
msg_fmt = _("The vpc ID '%(id)s' does not exist")
class InvalidInternetGatewayIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidInternetGatewayID.NotFound'
msg_fmt = _("The internetGateway ID '%(id)s' does not exist")
class InvalidSubnetIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidSubnetID.NotFound'
msg_fmt = _("The subnet ID '%(id)s' does not exist")
class InvalidNetworkInterfaceIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidNetworkInterfaceID.NotFound'
msg_fmt = _("Network interface %(id)s could not "
"be found.")
class InvalidAttachmentIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidAttachmentID.NotFound'
msg_fmt = _("Attachment %(id)s could not "
"be found.")
class InvalidInstanceIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidInstanceID.NotFound'
msg_fmt = _("The instance ID '%(id)s' does not exist")
class InvalidDhcpOptionsIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidDhcpOptionsID.NotFound'
msg_fmt = _("The dhcp options ID '%(id)s' does not exist")
class InvalidAddressNotFound(EC2NotFoundException):
ec2_code = 'InvalidAddress.NotFound'
msg_fmt = _('The specified elastic IP address %(ip)s cannot be found.')
class InvalidAllocationIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidAllocationID.NotFound'
msg_fmt = _("The allocation ID '%(id)s' does not exist")
class InvalidAssociationIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidAssociationID.NotFound'
msg_fmt = _("The association ID '%(id)s' does not exist")
class InvalidSecurityGroupIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidSecurityGroupID.NotFound'
msg_fmt = _("The securityGroup ID '%(id)s' does not exist")
class InvalidGroupNotFound(EC2NotFoundException):
ec2_code = 'InvalidGroup.NotFound'
msg_fmg = _("The security group ID '%(id)s' does not exist")
class InvalidPermissionNotFound(EC2NotFoundException):
ec2_code = 'InvalidPermission.NotFound'
msg_fmg = _('The specified permission does not exist')
class InvalidRouteTableIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidRouteTableID.NotFound'
msg_fmt = _("The routeTable ID '%(id)s' does not exist")
class InvalidRouteNotFound(EC2NotFoundException):
ec2_code = 'InvalidRoute.NotFound'
msg_fmt = _('No route with destination-cidr-block '
'%(destination_cidr_block)s in route table %(route_table_id)s')
class InvalidAMIIDNotFound(EC2NotFoundException):
ec2_code = 'InvalidAMIID.NotFound'
msg_fmt = _("The image id '[%(id)s]' does not exist")
class InvalidVolumeNotFound(EC2NotFoundException):
ec2_code = 'InvalidVolume.NotFound'
msg_fmt = _("The volume '%(id)s' does not exist.")
class InvalidSnapshotNotFound(EC2NotFoundException):
ec2_code = 'InvalidSnapshot.NotFound'
msg_fmt = _("Snapshot %(id)s could not be found.")
class InvalidKeypairNotFound(EC2NotFoundException):
ec2_code = 'InvalidKeyPair.NotFound'
msg_fmt = _("Keypair %(id)s is not found")
class InvalidAvailabilityZoneNotFound(EC2NotFoundException):
ec2_code = 'InvalidAvailabilityZone.NotFound'
msg_fmt = _("Availability zone %(id)s not found")
class ResourceLimitExceeded(EC2OverlimitException):
msg_fmt = _('You have reached the limit of %(resource)s')
class VpcLimitExceeded(EC2OverlimitException):
msg_fmt = _('The maximum number of VPCs has been reached.')
class SubnetLimitExceeded(EC2OverlimitException):
msg_fmt = _('You have reached the limit on the number of subnets that you '
'can create')
class NetworkInterfaceLimitExceeded(EC2OverlimitException):
msg_fmt = _('You have reached the limit of network interfaces for subnet'
'%(subnet_id)s.')
class AddressLimitExceeded(EC2OverlimitException):
msg_fmt = _('The maximum number of addresses has been reached.')
class SecurityGroupLimitExceeded(EC2OverlimitException):
msg_fmt = _('You have reached the limit of security groups')
class RulesPerSecurityGroupLimitExceeded(EC2OverlimitException):
msg_fmt = _("You've reached the limit on the number of rules that "
"you can add to a security group.")

View File

@ -613,10 +613,10 @@ class S3TestCase(base.ApiTestCase):
def test_s3_malicious_tarballs(self):
self.assertRaises(
exception.Invalid,
exception.EC2InvalidException,
image_api._s3_test_for_malicious_tarball,
"/unused", os.path.join(os.path.dirname(__file__), 'abs.tar.gz'))
self.assertRaises(
exception.Invalid,
exception.EC2InvalidException,
image_api._s3_test_for_malicious_tarball,
"/unused", os.path.join(os.path.dirname(__file__), 'rel.tar.gz'))

View File

@ -1478,7 +1478,7 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
get_os_image.return_value = os_image
self.assertRaises(
exception.ImageNotActive,
exception.InvalidAMIIDUnavailable,
instance_api._parse_image_parameters,
fake_context, fakes.random_ec2_id('ami'), None, None)
@ -1486,7 +1486,7 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
os_image.properties['image_state'] = 'decrypting'
self.assertRaises(
exception.ImageNotActive,
exception.InvalidAMIIDUnavailable,
instance_api._parse_image_parameters,
fake_context, fakes.random_ec2_id('ami'), None, None)

View File

@ -484,14 +484,14 @@ class Loader(object):
self.config_path = config_path
if not self.config_path:
raise exception.ConfigNotFound(path=config_path)
raise exception.EC2APIConfigNotFound(path=config_path)
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: `ec2api.exception.PasteAppNotFound`
:raises: `ec2api.exception.EC2APIPasteAppNotFound`
"""
try:
@ -500,4 +500,5 @@ class Loader(object):
return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError as err:
LOG.error(err)
raise exception.PasteAppNotFound(name=name, path=self.config_path)
raise exception.EC2APIPasteAppNotFound(name=name,
path=self.config_path)