• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
2# Copyright (c) 2010, Eucalyptus Systems, Inc.
3# All rights reserved.
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the
7# "Software"), to deal in the Software without restriction, including
8# without limitation the rights to use, copy, modify, merge, publish, dis-
9# tribute, sublicense, and/or sell copies of the Software, and to permit
10# persons to whom the Software is furnished to do so, subject to the fol-
11# lowing conditions:
12#
13# The above copyright notice and this permission notice shall be included
14# in all copies or substantial portions of the Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
18# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22# IN THE SOFTWARE.
23
24"""
25Exception classes - Subclassing allows you to check for specific errors
26"""
27import base64
28import xml.sax
29
30import boto
31
32from boto import handler
33from boto.compat import json, StandardError
34from boto.resultset import ResultSet
35
36
37class BotoClientError(StandardError):
38    """
39    General Boto Client error (error accessing AWS)
40    """
41    def __init__(self, reason, *args):
42        super(BotoClientError, self).__init__(reason, *args)
43        self.reason = reason
44
45    def __repr__(self):
46        return 'BotoClientError: %s' % self.reason
47
48    def __str__(self):
49        return 'BotoClientError: %s' % self.reason
50
51
52class SDBPersistenceError(StandardError):
53    pass
54
55
56class StoragePermissionsError(BotoClientError):
57    """
58    Permissions error when accessing a bucket or key on a storage service.
59    """
60    pass
61
62
63class S3PermissionsError(StoragePermissionsError):
64    """
65    Permissions error when accessing a bucket or key on S3.
66    """
67    pass
68
69
70class GSPermissionsError(StoragePermissionsError):
71    """
72    Permissions error when accessing a bucket or key on GS.
73    """
74    pass
75
76
77class BotoServerError(StandardError):
78    def __init__(self, status, reason, body=None, *args):
79        super(BotoServerError, self).__init__(status, reason, body, *args)
80        self.status = status
81        self.reason = reason
82        self.body = body or ''
83        self.request_id = None
84        self.error_code = None
85        self._error_message = None
86        self.message = ''
87        self.box_usage = None
88
89        if isinstance(self.body, bytes):
90            try:
91                self.body = self.body.decode('utf-8')
92            except UnicodeDecodeError:
93                boto.log.debug('Unable to decode body from bytes!')
94
95        # Attempt to parse the error response. If body isn't present,
96        # then just ignore the error response.
97        if self.body:
98            # Check if it looks like a ``dict``.
99            if hasattr(self.body, 'items'):
100                # It's not a string, so trying to parse it will fail.
101                # But since it's data, we can work with that.
102                self.request_id = self.body.get('RequestId', None)
103
104                if 'Error' in self.body:
105                    # XML-style
106                    error = self.body.get('Error', {})
107                    self.error_code = error.get('Code', None)
108                    self.message = error.get('Message', None)
109                else:
110                    # JSON-style.
111                    self.message = self.body.get('message', None)
112            else:
113                try:
114                    h = handler.XmlHandlerWrapper(self, self)
115                    h.parseString(self.body)
116                except (TypeError, xml.sax.SAXParseException):
117                    # What if it's JSON? Let's try that.
118                    try:
119                        parsed = json.loads(self.body)
120
121                        if 'RequestId' in parsed:
122                            self.request_id = parsed['RequestId']
123                        if 'Error' in parsed:
124                            if 'Code' in parsed['Error']:
125                                self.error_code = parsed['Error']['Code']
126                            if 'Message' in parsed['Error']:
127                                self.message = parsed['Error']['Message']
128
129                    except (TypeError, ValueError):
130                        # Remove unparsable message body so we don't include garbage
131                        # in exception. But first, save self.body in self.error_message
132                        # because occasionally we get error messages from Eucalyptus
133                        # that are just text strings that we want to preserve.
134                        self.message = self.body
135                        self.body = None
136
137    def __getattr__(self, name):
138        if name == 'error_message':
139            return self.message
140        if name == 'code':
141            return self.error_code
142        raise AttributeError
143
144    def __setattr__(self, name, value):
145        if name == 'error_message':
146            self.message = value
147        else:
148            super(BotoServerError, self).__setattr__(name, value)
149
150    def __repr__(self):
151        return '%s: %s %s\n%s' % (self.__class__.__name__,
152                                  self.status, self.reason, self.body)
153
154    def __str__(self):
155        return '%s: %s %s\n%s' % (self.__class__.__name__,
156                                  self.status, self.reason, self.body)
157
158    def startElement(self, name, attrs, connection):
159        pass
160
161    def endElement(self, name, value, connection):
162        if name in ('RequestId', 'RequestID'):
163            self.request_id = value
164        elif name == 'Code':
165            self.error_code = value
166        elif name == 'Message':
167            self.message = value
168        elif name == 'BoxUsage':
169            self.box_usage = value
170        return None
171
172    def _cleanupParsedProperties(self):
173        self.request_id = None
174        self.error_code = None
175        self.message = None
176        self.box_usage = None
177
178
179class ConsoleOutput(object):
180    def __init__(self, parent=None):
181        self.parent = parent
182        self.instance_id = None
183        self.timestamp = None
184        self.comment = None
185        self.output = None
186
187    def startElement(self, name, attrs, connection):
188        return None
189
190    def endElement(self, name, value, connection):
191        if name == 'instanceId':
192            self.instance_id = value
193        elif name == 'output':
194            self.output = base64.b64decode(value)
195        else:
196            setattr(self, name, value)
197
198
199class StorageCreateError(BotoServerError):
200    """
201    Error creating a bucket or key on a storage service.
202    """
203    def __init__(self, status, reason, body=None):
204        self.bucket = None
205        super(StorageCreateError, self).__init__(status, reason, body)
206
207    def endElement(self, name, value, connection):
208        if name == 'BucketName':
209            self.bucket = value
210        else:
211            return super(StorageCreateError, self).endElement(name, value, connection)
212
213
214class S3CreateError(StorageCreateError):
215    """
216    Error creating a bucket or key on S3.
217    """
218    pass
219
220
221class GSCreateError(StorageCreateError):
222    """
223    Error creating a bucket or key on GS.
224    """
225    pass
226
227
228class StorageCopyError(BotoServerError):
229    """
230    Error copying a key on a storage service.
231    """
232    pass
233
234
235class S3CopyError(StorageCopyError):
236    """
237    Error copying a key on S3.
238    """
239    pass
240
241
242class GSCopyError(StorageCopyError):
243    """
244    Error copying a key on GS.
245    """
246    pass
247
248
249class SQSError(BotoServerError):
250    """
251    General Error on Simple Queue Service.
252    """
253    def __init__(self, status, reason, body=None):
254        self.detail = None
255        self.type = None
256        super(SQSError, self).__init__(status, reason, body)
257
258    def startElement(self, name, attrs, connection):
259        return super(SQSError, self).startElement(name, attrs, connection)
260
261    def endElement(self, name, value, connection):
262        if name == 'Detail':
263            self.detail = value
264        elif name == 'Type':
265            self.type = value
266        else:
267            return super(SQSError, self).endElement(name, value, connection)
268
269    def _cleanupParsedProperties(self):
270        super(SQSError, self)._cleanupParsedProperties()
271        for p in ('detail', 'type'):
272            setattr(self, p, None)
273
274
275class SQSDecodeError(BotoClientError):
276    """
277    Error when decoding an SQS message.
278    """
279    def __init__(self, reason, message):
280        super(SQSDecodeError, self).__init__(reason, message)
281        self.message = message
282
283    def __repr__(self):
284        return 'SQSDecodeError: %s' % self.reason
285
286    def __str__(self):
287        return 'SQSDecodeError: %s' % self.reason
288
289
290class StorageResponseError(BotoServerError):
291    """
292    Error in response from a storage service.
293    """
294    def __init__(self, status, reason, body=None):
295        self.resource = None
296        super(StorageResponseError, self).__init__(status, reason, body)
297
298    def startElement(self, name, attrs, connection):
299        return super(StorageResponseError, self).startElement(
300            name, attrs, connection)
301
302    def endElement(self, name, value, connection):
303        if name == 'Resource':
304            self.resource = value
305        else:
306            return super(StorageResponseError, self).endElement(
307                name, value, connection)
308
309    def _cleanupParsedProperties(self):
310        super(StorageResponseError, self)._cleanupParsedProperties()
311        for p in ('resource'):
312            setattr(self, p, None)
313
314
315class S3ResponseError(StorageResponseError):
316    """
317    Error in response from S3.
318    """
319    pass
320
321
322class GSResponseError(StorageResponseError):
323    """
324    Error in response from GS.
325    """
326    pass
327
328
329class EC2ResponseError(BotoServerError):
330    """
331    Error in response from EC2.
332    """
333    def __init__(self, status, reason, body=None):
334        self.errors = None
335        self._errorResultSet = []
336        super(EC2ResponseError, self).__init__(status, reason, body)
337        self.errors = [
338            (e.error_code, e.error_message) for e in self._errorResultSet]
339        if len(self.errors):
340            self.error_code, self.error_message = self.errors[0]
341
342    def startElement(self, name, attrs, connection):
343        if name == 'Errors':
344            self._errorResultSet = ResultSet([('Error', _EC2Error)])
345            return self._errorResultSet
346        else:
347            return None
348
349    def endElement(self, name, value, connection):
350        if name == 'RequestID':
351            self.request_id = value
352        else:
353            return None  # don't call subclass here
354
355    def _cleanupParsedProperties(self):
356        super(EC2ResponseError, self)._cleanupParsedProperties()
357        self._errorResultSet = []
358        for p in ('errors'):
359            setattr(self, p, None)
360
361
362class JSONResponseError(BotoServerError):
363    """
364    This exception expects the fully parsed and decoded JSON response
365    body to be passed as the body parameter.
366
367    :ivar status: The HTTP status code.
368    :ivar reason: The HTTP reason message.
369    :ivar body: The Python dict that represents the decoded JSON
370        response body.
371    :ivar error_message: The full description of the AWS error encountered.
372    :ivar error_code: A short string that identifies the AWS error
373        (e.g. ConditionalCheckFailedException)
374    """
375    def __init__(self, status, reason, body=None, *args):
376        self.status = status
377        self.reason = reason
378        self.body = body
379        if self.body:
380            self.error_message = self.body.get('message', None)
381            self.error_code = self.body.get('__type', None)
382            if self.error_code:
383                self.error_code = self.error_code.split('#')[-1]
384
385
386class DynamoDBResponseError(JSONResponseError):
387    pass
388
389
390class SWFResponseError(JSONResponseError):
391    pass
392
393
394class EmrResponseError(BotoServerError):
395    """
396    Error in response from EMR
397    """
398    pass
399
400
401class _EC2Error(object):
402    def __init__(self, connection=None):
403        self.connection = connection
404        self.error_code = None
405        self.error_message = None
406
407    def startElement(self, name, attrs, connection):
408        return None
409
410    def endElement(self, name, value, connection):
411        if name == 'Code':
412            self.error_code = value
413        elif name == 'Message':
414            self.error_message = value
415        else:
416            return None
417
418
419class SDBResponseError(BotoServerError):
420    """
421    Error in responses from SDB.
422    """
423    pass
424
425
426class AWSConnectionError(BotoClientError):
427    """
428    General error connecting to Amazon Web Services.
429    """
430    pass
431
432
433class StorageDataError(BotoClientError):
434    """
435    Error receiving data from a storage service.
436    """
437    pass
438
439
440class S3DataError(StorageDataError):
441    """
442    Error receiving data from S3.
443    """
444    pass
445
446
447class GSDataError(StorageDataError):
448    """
449    Error receiving data from GS.
450    """
451    pass
452
453
454class InvalidUriError(Exception):
455    """Exception raised when URI is invalid."""
456
457    def __init__(self, message):
458        super(InvalidUriError, self).__init__(message)
459        self.message = message
460
461
462class InvalidAclError(Exception):
463    """Exception raised when ACL XML is invalid."""
464
465    def __init__(self, message):
466        super(InvalidAclError, self).__init__(message)
467        self.message = message
468
469
470class InvalidCorsError(Exception):
471    """Exception raised when CORS XML is invalid."""
472
473    def __init__(self, message):
474        super(InvalidCorsError, self).__init__(message)
475        self.message = message
476
477
478class NoAuthHandlerFound(Exception):
479    """Is raised when no auth handlers were found ready to authenticate."""
480    pass
481
482
483class InvalidLifecycleConfigError(Exception):
484    """Exception raised when GCS lifecycle configuration XML is invalid."""
485
486    def __init__(self, message):
487        super(InvalidLifecycleConfigError, self).__init__(message)
488        self.message = message
489
490
491# Enum class for resumable upload failure disposition.
492class ResumableTransferDisposition(object):
493    # START_OVER means an attempt to resume an existing transfer failed,
494    # and a new resumable upload should be attempted (without delay).
495    START_OVER = 'START_OVER'
496
497    # WAIT_BEFORE_RETRY means the resumable transfer failed but that it can
498    # be retried after a time delay within the current process.
499    WAIT_BEFORE_RETRY = 'WAIT_BEFORE_RETRY'
500
501    # ABORT_CUR_PROCESS means the resumable transfer failed and that
502    # delaying/retrying within the current process will not help. If
503    # resumable transfer included a state tracker file the upload can be
504    # retried again later, in another process (e.g., a later run of gsutil).
505    ABORT_CUR_PROCESS = 'ABORT_CUR_PROCESS'
506
507    # ABORT means the resumable transfer failed in a way that it does not
508    # make sense to continue in the current process, and further that the
509    # current tracker ID should not be preserved (in a tracker file if one
510    # was specified at resumable upload start time). If the user tries again
511    # later (e.g., a separate run of gsutil) it will get a new resumable
512    # upload ID.
513    ABORT = 'ABORT'
514
515
516class ResumableUploadException(Exception):
517    """
518    Exception raised for various resumable upload problems.
519
520    self.disposition is of type ResumableTransferDisposition.
521    """
522
523    def __init__(self, message, disposition):
524        super(ResumableUploadException, self).__init__(message, disposition)
525        self.message = message
526        self.disposition = disposition
527
528    def __repr__(self):
529        return 'ResumableUploadException("%s", %s)' % (
530            self.message, self.disposition)
531
532
533class ResumableDownloadException(Exception):
534    """
535    Exception raised for various resumable download problems.
536
537    self.disposition is of type ResumableTransferDisposition.
538    """
539
540    def __init__(self, message, disposition):
541        super(ResumableDownloadException, self).__init__(message, disposition)
542        self.message = message
543        self.disposition = disposition
544
545    def __repr__(self):
546        return 'ResumableDownloadException("%s", %s)' % (
547            self.message, self.disposition)
548
549
550class TooManyRecordsException(Exception):
551    """
552    Exception raised when a search of Route53 records returns more
553    records than requested.
554    """
555
556    def __init__(self, message):
557        super(TooManyRecordsException, self).__init__(message)
558        self.message = message
559
560
561class PleaseRetryException(Exception):
562    """
563    Indicates a request should be retried.
564    """
565    def __init__(self, message, response=None):
566        self.message = message
567        self.response = response
568
569    def __repr__(self):
570        return 'PleaseRetryException("%s", %s)' % (
571            self.message,
572            self.response
573        )
574