OLD | NEW |
(Empty) | |
| 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 """ |
| 25 Exception classes - Subclassing allows you to check for specific errors |
| 26 """ |
| 27 import base64 |
| 28 import xml.sax |
| 29 from boto import handler |
| 30 from boto.resultset import ResultSet |
| 31 |
| 32 |
| 33 class BotoClientError(StandardError): |
| 34 """ |
| 35 General Boto Client error (error accessing AWS) |
| 36 """ |
| 37 |
| 38 def __init__(self, reason, *args): |
| 39 StandardError.__init__(self, reason, *args) |
| 40 self.reason = reason |
| 41 |
| 42 def __repr__(self): |
| 43 return 'BotoClientError: %s' % self.reason |
| 44 |
| 45 def __str__(self): |
| 46 return 'BotoClientError: %s' % self.reason |
| 47 |
| 48 class SDBPersistenceError(StandardError): |
| 49 |
| 50 pass |
| 51 |
| 52 class StoragePermissionsError(BotoClientError): |
| 53 """ |
| 54 Permissions error when accessing a bucket or key on a storage service. |
| 55 """ |
| 56 pass |
| 57 |
| 58 class S3PermissionsError(StoragePermissionsError): |
| 59 """ |
| 60 Permissions error when accessing a bucket or key on S3. |
| 61 """ |
| 62 pass |
| 63 |
| 64 class GSPermissionsError(StoragePermissionsError): |
| 65 """ |
| 66 Permissions error when accessing a bucket or key on GS. |
| 67 """ |
| 68 pass |
| 69 |
| 70 class BotoServerError(StandardError): |
| 71 |
| 72 def __init__(self, status, reason, body=None, *args): |
| 73 StandardError.__init__(self, status, reason, body, *args) |
| 74 self.status = status |
| 75 self.reason = reason |
| 76 self.body = body or '' |
| 77 self.request_id = None |
| 78 self.error_code = None |
| 79 self.error_message = None |
| 80 self.box_usage = None |
| 81 |
| 82 # Attempt to parse the error response. If body isn't present, |
| 83 # then just ignore the error response. |
| 84 if self.body: |
| 85 try: |
| 86 h = handler.XmlHandler(self, self) |
| 87 xml.sax.parseString(self.body, h) |
| 88 except (TypeError, xml.sax.SAXParseException), pe: |
| 89 # Remove unparsable message body so we don't include garbage |
| 90 # in exception. But first, save self.body in self.error_message |
| 91 # because occasionally we get error messages from Eucalyptus |
| 92 # that are just text strings that we want to preserve. |
| 93 self.error_message = self.body |
| 94 self.body = None |
| 95 |
| 96 def __getattr__(self, name): |
| 97 if name == 'message': |
| 98 return self.error_message |
| 99 if name == 'code': |
| 100 return self.error_code |
| 101 raise AttributeError |
| 102 |
| 103 def __repr__(self): |
| 104 return '%s: %s %s\n%s' % (self.__class__.__name__, |
| 105 self.status, self.reason, self.body) |
| 106 |
| 107 def __str__(self): |
| 108 return '%s: %s %s\n%s' % (self.__class__.__name__, |
| 109 self.status, self.reason, self.body) |
| 110 |
| 111 def startElement(self, name, attrs, connection): |
| 112 pass |
| 113 |
| 114 def endElement(self, name, value, connection): |
| 115 if name in ('RequestId', 'RequestID'): |
| 116 self.request_id = value |
| 117 elif name == 'Code': |
| 118 self.error_code = value |
| 119 elif name == 'Message': |
| 120 self.error_message = value |
| 121 elif name == 'BoxUsage': |
| 122 self.box_usage = value |
| 123 return None |
| 124 |
| 125 def _cleanupParsedProperties(self): |
| 126 self.request_id = None |
| 127 self.error_code = None |
| 128 self.error_message = None |
| 129 self.box_usage = None |
| 130 |
| 131 class ConsoleOutput: |
| 132 |
| 133 def __init__(self, parent=None): |
| 134 self.parent = parent |
| 135 self.instance_id = None |
| 136 self.timestamp = None |
| 137 self.comment = None |
| 138 self.output = None |
| 139 |
| 140 def startElement(self, name, attrs, connection): |
| 141 return None |
| 142 |
| 143 def endElement(self, name, value, connection): |
| 144 if name == 'instanceId': |
| 145 self.instance_id = value |
| 146 elif name == 'output': |
| 147 self.output = base64.b64decode(value) |
| 148 else: |
| 149 setattr(self, name, value) |
| 150 |
| 151 class StorageCreateError(BotoServerError): |
| 152 """ |
| 153 Error creating a bucket or key on a storage service. |
| 154 """ |
| 155 def __init__(self, status, reason, body=None): |
| 156 self.bucket = None |
| 157 BotoServerError.__init__(self, status, reason, body) |
| 158 |
| 159 def endElement(self, name, value, connection): |
| 160 if name == 'BucketName': |
| 161 self.bucket = value |
| 162 else: |
| 163 return BotoServerError.endElement(self, name, value, connection) |
| 164 |
| 165 class S3CreateError(StorageCreateError): |
| 166 """ |
| 167 Error creating a bucket or key on S3. |
| 168 """ |
| 169 pass |
| 170 |
| 171 class GSCreateError(StorageCreateError): |
| 172 """ |
| 173 Error creating a bucket or key on GS. |
| 174 """ |
| 175 pass |
| 176 |
| 177 class StorageCopyError(BotoServerError): |
| 178 """ |
| 179 Error copying a key on a storage service. |
| 180 """ |
| 181 pass |
| 182 |
| 183 class S3CopyError(StorageCopyError): |
| 184 """ |
| 185 Error copying a key on S3. |
| 186 """ |
| 187 pass |
| 188 |
| 189 class GSCopyError(StorageCopyError): |
| 190 """ |
| 191 Error copying a key on GS. |
| 192 """ |
| 193 pass |
| 194 |
| 195 class SQSError(BotoServerError): |
| 196 """ |
| 197 General Error on Simple Queue Service. |
| 198 """ |
| 199 def __init__(self, status, reason, body=None): |
| 200 self.detail = None |
| 201 self.type = None |
| 202 BotoServerError.__init__(self, status, reason, body) |
| 203 |
| 204 def startElement(self, name, attrs, connection): |
| 205 return BotoServerError.startElement(self, name, attrs, connection) |
| 206 |
| 207 def endElement(self, name, value, connection): |
| 208 if name == 'Detail': |
| 209 self.detail = value |
| 210 elif name == 'Type': |
| 211 self.type = value |
| 212 else: |
| 213 return BotoServerError.endElement(self, name, value, connection) |
| 214 |
| 215 def _cleanupParsedProperties(self): |
| 216 BotoServerError._cleanupParsedProperties(self) |
| 217 for p in ('detail', 'type'): |
| 218 setattr(self, p, None) |
| 219 |
| 220 class SQSDecodeError(BotoClientError): |
| 221 """ |
| 222 Error when decoding an SQS message. |
| 223 """ |
| 224 def __init__(self, reason, message): |
| 225 BotoClientError.__init__(self, reason, message) |
| 226 self.message = message |
| 227 |
| 228 def __repr__(self): |
| 229 return 'SQSDecodeError: %s' % self.reason |
| 230 |
| 231 def __str__(self): |
| 232 return 'SQSDecodeError: %s' % self.reason |
| 233 |
| 234 class StorageResponseError(BotoServerError): |
| 235 """ |
| 236 Error in response from a storage service. |
| 237 """ |
| 238 def __init__(self, status, reason, body=None): |
| 239 self.resource = None |
| 240 BotoServerError.__init__(self, status, reason, body) |
| 241 |
| 242 def startElement(self, name, attrs, connection): |
| 243 return BotoServerError.startElement(self, name, attrs, connection) |
| 244 |
| 245 def endElement(self, name, value, connection): |
| 246 if name == 'Resource': |
| 247 self.resource = value |
| 248 else: |
| 249 return BotoServerError.endElement(self, name, value, connection) |
| 250 |
| 251 def _cleanupParsedProperties(self): |
| 252 BotoServerError._cleanupParsedProperties(self) |
| 253 for p in ('resource'): |
| 254 setattr(self, p, None) |
| 255 |
| 256 class S3ResponseError(StorageResponseError): |
| 257 """ |
| 258 Error in response from S3. |
| 259 """ |
| 260 pass |
| 261 |
| 262 class GSResponseError(StorageResponseError): |
| 263 """ |
| 264 Error in response from GS. |
| 265 """ |
| 266 pass |
| 267 |
| 268 class EC2ResponseError(BotoServerError): |
| 269 """ |
| 270 Error in response from EC2. |
| 271 """ |
| 272 |
| 273 def __init__(self, status, reason, body=None): |
| 274 self.errors = None |
| 275 self._errorResultSet = [] |
| 276 BotoServerError.__init__(self, status, reason, body) |
| 277 self.errors = [ (e.error_code, e.error_message) \ |
| 278 for e in self._errorResultSet ] |
| 279 if len(self.errors): |
| 280 self.error_code, self.error_message = self.errors[0] |
| 281 |
| 282 def startElement(self, name, attrs, connection): |
| 283 if name == 'Errors': |
| 284 self._errorResultSet = ResultSet([('Error', _EC2Error)]) |
| 285 return self._errorResultSet |
| 286 else: |
| 287 return None |
| 288 |
| 289 def endElement(self, name, value, connection): |
| 290 if name == 'RequestID': |
| 291 self.request_id = value |
| 292 else: |
| 293 return None # don't call subclass here |
| 294 |
| 295 def _cleanupParsedProperties(self): |
| 296 BotoServerError._cleanupParsedProperties(self) |
| 297 self._errorResultSet = [] |
| 298 for p in ('errors'): |
| 299 setattr(self, p, None) |
| 300 |
| 301 class JSONResponseError(BotoServerError): |
| 302 """ |
| 303 This exception expects the fully parsed and decoded JSON response |
| 304 body to be passed as the body parameter. |
| 305 |
| 306 :ivar status: The HTTP status code. |
| 307 :ivar reason: The HTTP reason message. |
| 308 :ivar body: The Python dict that represents the decoded JSON |
| 309 response body. |
| 310 :ivar error_message: The full description of the AWS error encountered. |
| 311 :ivar error_code: A short string that identifies the AWS error |
| 312 (e.g. ConditionalCheckFailedException) |
| 313 """ |
| 314 def __init__(self, status, reason, body=None, *args): |
| 315 self.status = status |
| 316 self.reason = reason |
| 317 self.body = body |
| 318 if self.body: |
| 319 self.error_message = self.body.get('message', None) |
| 320 self.error_code = self.body.get('__type', None) |
| 321 if self.error_code: |
| 322 self.error_code = self.error_code.split('#')[-1] |
| 323 |
| 324 |
| 325 class DynamoDBResponseError(JSONResponseError): |
| 326 pass |
| 327 |
| 328 |
| 329 class SWFResponseError(JSONResponseError): |
| 330 pass |
| 331 |
| 332 |
| 333 class EmrResponseError(BotoServerError): |
| 334 """ |
| 335 Error in response from EMR |
| 336 """ |
| 337 pass |
| 338 |
| 339 class _EC2Error: |
| 340 |
| 341 def __init__(self, connection=None): |
| 342 self.connection = connection |
| 343 self.error_code = None |
| 344 self.error_message = None |
| 345 |
| 346 def startElement(self, name, attrs, connection): |
| 347 return None |
| 348 |
| 349 def endElement(self, name, value, connection): |
| 350 if name == 'Code': |
| 351 self.error_code = value |
| 352 elif name == 'Message': |
| 353 self.error_message = value |
| 354 else: |
| 355 return None |
| 356 |
| 357 class SDBResponseError(BotoServerError): |
| 358 """ |
| 359 Error in responses from SDB. |
| 360 """ |
| 361 pass |
| 362 |
| 363 class AWSConnectionError(BotoClientError): |
| 364 """ |
| 365 General error connecting to Amazon Web Services. |
| 366 """ |
| 367 pass |
| 368 |
| 369 class StorageDataError(BotoClientError): |
| 370 """ |
| 371 Error receiving data from a storage service. |
| 372 """ |
| 373 pass |
| 374 |
| 375 class S3DataError(StorageDataError): |
| 376 """ |
| 377 Error receiving data from S3. |
| 378 """ |
| 379 pass |
| 380 |
| 381 class GSDataError(StorageDataError): |
| 382 """ |
| 383 Error receiving data from GS. |
| 384 """ |
| 385 pass |
| 386 |
| 387 class InvalidUriError(Exception): |
| 388 """Exception raised when URI is invalid.""" |
| 389 |
| 390 def __init__(self, message): |
| 391 Exception.__init__(self, message) |
| 392 self.message = message |
| 393 |
| 394 class InvalidAclError(Exception): |
| 395 """Exception raised when ACL XML is invalid.""" |
| 396 |
| 397 def __init__(self, message): |
| 398 Exception.__init__(self, message) |
| 399 self.message = message |
| 400 |
| 401 class InvalidCorsError(Exception): |
| 402 """Exception raised when CORS XML is invalid.""" |
| 403 |
| 404 def __init__(self, message): |
| 405 Exception.__init__(self, message) |
| 406 self.message = message |
| 407 |
| 408 class NoAuthHandlerFound(Exception): |
| 409 """Is raised when no auth handlers were found ready to authenticate.""" |
| 410 pass |
| 411 |
| 412 # Enum class for resumable upload failure disposition. |
| 413 class ResumableTransferDisposition(object): |
| 414 # START_OVER means an attempt to resume an existing transfer failed, |
| 415 # and a new resumable upload should be attempted (without delay). |
| 416 START_OVER = 'START_OVER' |
| 417 |
| 418 # WAIT_BEFORE_RETRY means the resumable transfer failed but that it can |
| 419 # be retried after a time delay within the current process. |
| 420 WAIT_BEFORE_RETRY = 'WAIT_BEFORE_RETRY' |
| 421 |
| 422 # ABORT_CUR_PROCESS means the resumable transfer failed and that |
| 423 # delaying/retrying within the current process will not help. If |
| 424 # resumable transfer included a state tracker file the upload can be |
| 425 # retried again later, in another process (e.g., a later run of gsutil). |
| 426 ABORT_CUR_PROCESS = 'ABORT_CUR_PROCESS' |
| 427 |
| 428 # ABORT means the resumable transfer failed in a way that it does not |
| 429 # make sense to continue in the current process, and further that the |
| 430 # current tracker ID should not be preserved (in a tracker file if one |
| 431 # was specified at resumable upload start time). If the user tries again |
| 432 # later (e.g., a separate run of gsutil) it will get a new resumable |
| 433 # upload ID. |
| 434 ABORT = 'ABORT' |
| 435 |
| 436 class ResumableUploadException(Exception): |
| 437 """ |
| 438 Exception raised for various resumable upload problems. |
| 439 |
| 440 self.disposition is of type ResumableTransferDisposition. |
| 441 """ |
| 442 |
| 443 def __init__(self, message, disposition): |
| 444 Exception.__init__(self, message, disposition) |
| 445 self.message = message |
| 446 self.disposition = disposition |
| 447 |
| 448 def __repr__(self): |
| 449 return 'ResumableUploadException("%s", %s)' % ( |
| 450 self.message, self.disposition) |
| 451 |
| 452 class ResumableDownloadException(Exception): |
| 453 """ |
| 454 Exception raised for various resumable download problems. |
| 455 |
| 456 self.disposition is of type ResumableTransferDisposition. |
| 457 """ |
| 458 |
| 459 def __init__(self, message, disposition): |
| 460 Exception.__init__(self, message, disposition) |
| 461 self.message = message |
| 462 self.disposition = disposition |
| 463 |
| 464 def __repr__(self): |
| 465 return 'ResumableDownloadException("%s", %s)' % ( |
| 466 self.message, self.disposition) |
| 467 |
| 468 class TooManyRecordsException(Exception): |
| 469 """ |
| 470 Exception raised when a search of Route53 records returns more |
| 471 records than requested. |
| 472 """ |
| 473 |
| 474 def __init__(self, message): |
| 475 Exception.__init__(self, message) |
| 476 self.message = message |
OLD | NEW |