594 lines
17 KiB
Python
594 lines
17 KiB
Python
|
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
# copy of this software and associated documentation files (the
|
||
|
# "Software"), to deal in the Software without restriction, including
|
||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||
|
# lowing conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be included
|
||
|
# in all copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
|
# IN THE SOFTWARE.
|
||
|
|
||
|
"""
|
||
|
Exception classes - Subclassing allows you to check for specific errors
|
||
|
"""
|
||
|
import base64
|
||
|
import xml.sax
|
||
|
|
||
|
import boto
|
||
|
|
||
|
from boto import handler
|
||
|
from boto.compat import json, StandardError
|
||
|
from boto.resultset import ResultSet
|
||
|
|
||
|
|
||
|
class BotoClientError(StandardError):
|
||
|
"""
|
||
|
General Boto Client error (error accessing AWS)
|
||
|
"""
|
||
|
def __init__(self, reason, *args):
|
||
|
super(BotoClientError, self).__init__(reason, *args)
|
||
|
self.reason = reason
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'BotoClientError: %s' % self.reason
|
||
|
|
||
|
def __str__(self):
|
||
|
return 'BotoClientError: %s' % self.reason
|
||
|
|
||
|
|
||
|
class SDBPersistenceError(StandardError):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class StoragePermissionsError(BotoClientError):
|
||
|
"""
|
||
|
Permissions error when accessing a bucket or key on a storage service.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class S3PermissionsError(StoragePermissionsError):
|
||
|
"""
|
||
|
Permissions error when accessing a bucket or key on S3.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GSPermissionsError(StoragePermissionsError):
|
||
|
"""
|
||
|
Permissions error when accessing a bucket or key on GS.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class BotoServerError(StandardError):
|
||
|
def __init__(self, status, reason, body=None, *args):
|
||
|
super(BotoServerError, self).__init__(status, reason, body, *args)
|
||
|
self.status = status
|
||
|
self.reason = reason
|
||
|
self.body = body or ''
|
||
|
self.request_id = None
|
||
|
self.error_code = None
|
||
|
self._error_message = None
|
||
|
self.message = ''
|
||
|
self.box_usage = None
|
||
|
|
||
|
if isinstance(self.body, bytes):
|
||
|
try:
|
||
|
self.body = self.body.decode('utf-8')
|
||
|
except UnicodeDecodeError:
|
||
|
boto.log.debug('Unable to decode body from bytes!')
|
||
|
|
||
|
# Attempt to parse the error response. If body isn't present,
|
||
|
# then just ignore the error response.
|
||
|
if self.body:
|
||
|
# Check if it looks like a ``dict``.
|
||
|
if hasattr(self.body, 'items'):
|
||
|
# It's not a string, so trying to parse it will fail.
|
||
|
# But since it's data, we can work with that.
|
||
|
self.request_id = self.body.get('RequestId', None)
|
||
|
|
||
|
if 'Error' in self.body:
|
||
|
# XML-style
|
||
|
error = self.body.get('Error', {})
|
||
|
self.error_code = error.get('Code', None)
|
||
|
self.message = error.get('Message', None)
|
||
|
else:
|
||
|
# JSON-style.
|
||
|
self.message = self.body.get('message', None)
|
||
|
else:
|
||
|
try:
|
||
|
h = handler.XmlHandlerWrapper(self, self)
|
||
|
h.parseString(self.body)
|
||
|
except (TypeError, xml.sax.SAXParseException):
|
||
|
# What if it's JSON? Let's try that.
|
||
|
try:
|
||
|
parsed = json.loads(self.body)
|
||
|
|
||
|
if 'RequestId' in parsed:
|
||
|
self.request_id = parsed['RequestId']
|
||
|
if 'Error' in parsed:
|
||
|
if 'Code' in parsed['Error']:
|
||
|
self.error_code = parsed['Error']['Code']
|
||
|
if 'Message' in parsed['Error']:
|
||
|
self.message = parsed['Error']['Message']
|
||
|
|
||
|
except (TypeError, ValueError):
|
||
|
# Remove unparsable message body so we don't include garbage
|
||
|
# in exception. But first, save self.body in self.error_message
|
||
|
# because occasionally we get error messages from Eucalyptus
|
||
|
# that are just text strings that we want to preserve.
|
||
|
self.message = self.body
|
||
|
self.body = None
|
||
|
|
||
|
def __getattr__(self, name):
|
||
|
if name == 'error_message':
|
||
|
return self.message
|
||
|
if name == 'code':
|
||
|
return self.error_code
|
||
|
raise AttributeError
|
||
|
|
||
|
def __setattr__(self, name, value):
|
||
|
if name == 'error_message':
|
||
|
self.message = value
|
||
|
else:
|
||
|
super(BotoServerError, self).__setattr__(name, value)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '%s: %s %s\n%s' % (self.__class__.__name__,
|
||
|
self.status, self.reason, self.body)
|
||
|
|
||
|
def __str__(self):
|
||
|
return '%s: %s %s\n%s' % (self.__class__.__name__,
|
||
|
self.status, self.reason, self.body)
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
pass
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name in ('RequestId', 'RequestID'):
|
||
|
self.request_id = value
|
||
|
elif name == 'Code':
|
||
|
self.error_code = value
|
||
|
elif name == 'Message':
|
||
|
self.message = value
|
||
|
elif name == 'BoxUsage':
|
||
|
self.box_usage = value
|
||
|
return None
|
||
|
|
||
|
def _cleanupParsedProperties(self):
|
||
|
self.request_id = None
|
||
|
self.error_code = None
|
||
|
self.message = None
|
||
|
self.box_usage = None
|
||
|
|
||
|
|
||
|
class ConsoleOutput(object):
|
||
|
def __init__(self, parent=None):
|
||
|
self.parent = parent
|
||
|
self.instance_id = None
|
||
|
self.timestamp = None
|
||
|
self.comment = None
|
||
|
self.output = None
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
return None
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'instanceId':
|
||
|
self.instance_id = value
|
||
|
elif name == 'output':
|
||
|
self.output = base64.b64decode(value)
|
||
|
else:
|
||
|
setattr(self, name, value)
|
||
|
|
||
|
|
||
|
class StorageCreateError(BotoServerError):
|
||
|
"""
|
||
|
Error creating a bucket or key on a storage service.
|
||
|
"""
|
||
|
def __init__(self, status, reason, body=None):
|
||
|
self.bucket = None
|
||
|
super(StorageCreateError, self).__init__(status, reason, body)
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'BucketName':
|
||
|
self.bucket = value
|
||
|
else:
|
||
|
return super(StorageCreateError, self).endElement(name, value, connection)
|
||
|
|
||
|
|
||
|
class S3CreateError(StorageCreateError):
|
||
|
"""
|
||
|
Error creating a bucket or key on S3.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GSCreateError(StorageCreateError):
|
||
|
"""
|
||
|
Error creating a bucket or key on GS.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class StorageCopyError(BotoServerError):
|
||
|
"""
|
||
|
Error copying a key on a storage service.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class S3CopyError(StorageCopyError):
|
||
|
"""
|
||
|
Error copying a key on S3.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GSCopyError(StorageCopyError):
|
||
|
"""
|
||
|
Error copying a key on GS.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class SQSError(BotoServerError):
|
||
|
"""
|
||
|
General Error on Simple Queue Service.
|
||
|
"""
|
||
|
def __init__(self, status, reason, body=None):
|
||
|
self.detail = None
|
||
|
self.type = None
|
||
|
super(SQSError, self).__init__(status, reason, body)
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
return super(SQSError, self).startElement(name, attrs, connection)
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'Detail':
|
||
|
self.detail = value
|
||
|
elif name == 'Type':
|
||
|
self.type = value
|
||
|
else:
|
||
|
return super(SQSError, self).endElement(name, value, connection)
|
||
|
|
||
|
def _cleanupParsedProperties(self):
|
||
|
super(SQSError, self)._cleanupParsedProperties()
|
||
|
for p in ('detail', 'type'):
|
||
|
setattr(self, p, None)
|
||
|
|
||
|
|
||
|
class SQSDecodeError(BotoClientError):
|
||
|
"""
|
||
|
Error when decoding an SQS message.
|
||
|
"""
|
||
|
def __init__(self, reason, message):
|
||
|
super(SQSDecodeError, self).__init__(reason, message)
|
||
|
self.message = message
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'SQSDecodeError: %s' % self.reason
|
||
|
|
||
|
def __str__(self):
|
||
|
return 'SQSDecodeError: %s' % self.reason
|
||
|
|
||
|
|
||
|
class StorageResponseError(BotoServerError):
|
||
|
"""
|
||
|
Error in response from a storage service.
|
||
|
"""
|
||
|
def __init__(self, status, reason, body=None):
|
||
|
self.resource = None
|
||
|
super(StorageResponseError, self).__init__(status, reason, body)
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
return super(StorageResponseError, self).startElement(
|
||
|
name, attrs, connection)
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'Resource':
|
||
|
self.resource = value
|
||
|
else:
|
||
|
return super(StorageResponseError, self).endElement(
|
||
|
name, value, connection)
|
||
|
|
||
|
def _cleanupParsedProperties(self):
|
||
|
super(StorageResponseError, self)._cleanupParsedProperties()
|
||
|
for p in ('resource'):
|
||
|
setattr(self, p, None)
|
||
|
|
||
|
|
||
|
class S3ResponseError(StorageResponseError):
|
||
|
"""
|
||
|
Error in response from S3.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GSResponseError(StorageResponseError):
|
||
|
"""
|
||
|
Error in response from GS.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class EC2ResponseError(BotoServerError):
|
||
|
"""
|
||
|
Error in response from EC2.
|
||
|
"""
|
||
|
def __init__(self, status, reason, body=None):
|
||
|
self.errors = None
|
||
|
self._errorResultSet = []
|
||
|
super(EC2ResponseError, self).__init__(status, reason, body)
|
||
|
self.errors = [
|
||
|
(e.error_code, e.error_message) for e in self._errorResultSet]
|
||
|
if len(self.errors):
|
||
|
self.error_code, self.error_message = self.errors[0]
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
if name == 'Errors':
|
||
|
self._errorResultSet = ResultSet([('Error', _EC2Error)])
|
||
|
return self._errorResultSet
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'RequestID':
|
||
|
self.request_id = value
|
||
|
else:
|
||
|
return None # don't call subclass here
|
||
|
|
||
|
def _cleanupParsedProperties(self):
|
||
|
super(EC2ResponseError, self)._cleanupParsedProperties()
|
||
|
self._errorResultSet = []
|
||
|
for p in ('errors'):
|
||
|
setattr(self, p, None)
|
||
|
|
||
|
|
||
|
class JSONResponseError(BotoServerError):
|
||
|
"""
|
||
|
This exception expects the fully parsed and decoded JSON response
|
||
|
body to be passed as the body parameter.
|
||
|
|
||
|
:ivar status: The HTTP status code.
|
||
|
:ivar reason: The HTTP reason message.
|
||
|
:ivar body: The Python dict that represents the decoded JSON
|
||
|
response body.
|
||
|
:ivar error_message: The full description of the AWS error encountered.
|
||
|
:ivar error_code: A short string that identifies the AWS error
|
||
|
(e.g. ConditionalCheckFailedException)
|
||
|
"""
|
||
|
def __init__(self, status, reason, body=None, *args):
|
||
|
self.status = status
|
||
|
self.reason = reason
|
||
|
self.body = body
|
||
|
if self.body:
|
||
|
self.error_message = self.body.get('message', None)
|
||
|
self.error_code = self.body.get('__type', None)
|
||
|
if self.error_code:
|
||
|
self.error_code = self.error_code.split('#')[-1]
|
||
|
|
||
|
|
||
|
class DynamoDBResponseError(JSONResponseError):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class SWFResponseError(JSONResponseError):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class EmrResponseError(BotoServerError):
|
||
|
"""
|
||
|
Error in response from EMR
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class _EC2Error(object):
|
||
|
def __init__(self, connection=None):
|
||
|
self.connection = connection
|
||
|
self.error_code = None
|
||
|
self.error_message = None
|
||
|
|
||
|
def startElement(self, name, attrs, connection):
|
||
|
return None
|
||
|
|
||
|
def endElement(self, name, value, connection):
|
||
|
if name == 'Code':
|
||
|
self.error_code = value
|
||
|
elif name == 'Message':
|
||
|
self.error_message = value
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
|
||
|
class SDBResponseError(BotoServerError):
|
||
|
"""
|
||
|
Error in responses from SDB.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class AWSConnectionError(BotoClientError):
|
||
|
"""
|
||
|
General error connecting to Amazon Web Services.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class StorageDataError(BotoClientError):
|
||
|
"""
|
||
|
Error receiving data from a storage service.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class S3DataError(StorageDataError):
|
||
|
"""
|
||
|
Error receiving data from S3.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GSDataError(StorageDataError):
|
||
|
"""
|
||
|
Error receiving data from GS.
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class InvalidUriError(Exception):
|
||
|
"""Exception raised when URI is invalid."""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(InvalidUriError, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class InvalidAclError(Exception):
|
||
|
"""Exception raised when ACL XML is invalid."""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(InvalidAclError, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class InvalidCorsError(Exception):
|
||
|
"""Exception raised when CORS XML is invalid."""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(InvalidCorsError, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class InvalidEncryptionConfigError(Exception):
|
||
|
"""Exception raised when GCS encryption configuration XML is invalid."""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(InvalidEncryptionConfigError, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class InvalidLifecycleConfigError(Exception):
|
||
|
"""Exception raised when GCS lifecycle configuration XML is invalid."""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(InvalidLifecycleConfigError, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class NoAuthHandlerFound(Exception):
|
||
|
"""Is raised when no auth handlers were found ready to authenticate."""
|
||
|
pass
|
||
|
|
||
|
|
||
|
# Enum class for resumable upload failure disposition.
|
||
|
class ResumableTransferDisposition(object):
|
||
|
# START_OVER means an attempt to resume an existing transfer failed,
|
||
|
# and a new resumable upload should be attempted (without delay).
|
||
|
START_OVER = 'START_OVER'
|
||
|
|
||
|
# WAIT_BEFORE_RETRY means the resumable transfer failed but that it can
|
||
|
# be retried after a time delay within the current process.
|
||
|
WAIT_BEFORE_RETRY = 'WAIT_BEFORE_RETRY'
|
||
|
|
||
|
# ABORT_CUR_PROCESS means the resumable transfer failed and that
|
||
|
# delaying/retrying within the current process will not help. If
|
||
|
# resumable transfer included a state tracker file the upload can be
|
||
|
# retried again later, in another process (e.g., a later run of gsutil).
|
||
|
ABORT_CUR_PROCESS = 'ABORT_CUR_PROCESS'
|
||
|
|
||
|
# ABORT means the resumable transfer failed in a way that it does not
|
||
|
# make sense to continue in the current process, and further that the
|
||
|
# current tracker ID should not be preserved (in a tracker file if one
|
||
|
# was specified at resumable upload start time). If the user tries again
|
||
|
# later (e.g., a separate run of gsutil) it will get a new resumable
|
||
|
# upload ID.
|
||
|
ABORT = 'ABORT'
|
||
|
|
||
|
|
||
|
class ResumableUploadException(Exception):
|
||
|
"""
|
||
|
Exception raised for various resumable upload problems.
|
||
|
|
||
|
self.disposition is of type ResumableTransferDisposition.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, message, disposition):
|
||
|
super(ResumableUploadException, self).__init__(message, disposition)
|
||
|
self.message = message
|
||
|
self.disposition = disposition
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'ResumableUploadException("%s", %s)' % (
|
||
|
self.message, self.disposition)
|
||
|
|
||
|
|
||
|
class ResumableDownloadException(Exception):
|
||
|
"""
|
||
|
Exception raised for various resumable download problems.
|
||
|
|
||
|
self.disposition is of type ResumableTransferDisposition.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, message, disposition):
|
||
|
super(ResumableDownloadException, self).__init__(message, disposition)
|
||
|
self.message = message
|
||
|
self.disposition = disposition
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'ResumableDownloadException("%s", %s)' % (
|
||
|
self.message, self.disposition)
|
||
|
|
||
|
|
||
|
class TooManyRecordsException(Exception):
|
||
|
"""
|
||
|
Exception raised when a search of Route53 records returns more
|
||
|
records than requested.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, message):
|
||
|
super(TooManyRecordsException, self).__init__(message)
|
||
|
self.message = message
|
||
|
|
||
|
|
||
|
class PleaseRetryException(Exception):
|
||
|
"""
|
||
|
Indicates a request should be retried.
|
||
|
"""
|
||
|
def __init__(self, message, response=None):
|
||
|
self.message = message
|
||
|
self.response = response
|
||
|
|
||
|
def __repr__(self):
|
||
|
return 'PleaseRetryException("%s", %s)' % (
|
||
|
self.message,
|
||
|
self.response
|
||
|
)
|
||
|
|
||
|
|
||
|
class InvalidInstanceMetadataError(Exception):
|
||
|
MSG = (
|
||
|
"You can set the 'metadata_service_num_attempts' "
|
||
|
"in your boto config file to increase the number "
|
||
|
"of times boto will attempt to retrieve "
|
||
|
"credentials from the instance metadata service."
|
||
|
)
|
||
|
def __init__(self, msg):
|
||
|
final_msg = msg + '\n' + self.MSG
|
||
|
super(InvalidInstanceMetadataError, self).__init__(final_msg)
|