You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

593 lines
17 KiB

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