Repository: boto/botocore
Pull request #957 (2016-06-21 15:41:29)

Waiter throw ClientError for unexpected error response
# Waiter should throw ClientError for unexpected error response

## Problem
During a waiters wait() routine, if the botocore client throws a ClientError, and there is no Acceptor in the JSON configured to handle that specific ClientError response, then a less informative WaiterError is thrown.

In doing so, error response information is lost (such as the Error Code), making it impossible to accurately detect certain errors (namely, 5xx errors and ThrottlingException, per http://docs.aws.amazon.com/general/latest/gr/api-retries.html

This "WaiterError" is caused by a regular ClientError, and has no specific connection to Waiter logic, other than it happened during waiting (unlike other WaiterErrors, which are for waiter-specific failure conditions like "max retries exceeded")

## Solution

If a ClientError occurs, and there is no Waiter logic ("acceptors") that handles it, then propagate a ClientError with all of the original information

### BTW...

tests/unit/test_waters.TestWaitersObjects.test_unspecified_errors_propagate_error_code does not actually test that unspecified errors propogate the Error Code; it tests that they propogate the Error Message (won't matter if this change gets submitted)

If this change is rejected, please consider actually including the Error Code somewhere in WaiterError. Maybe also the HTTP Response Code?


Patch:
diff --git a/botocore/waiter.py b/botocore/waiter.py
index 6f41f8a4f..a0d2834bc 100644
--- a/botocore/waiter.py
+++ b/botocore/waiter.py
@@ -303,11 +303,11 @@ class Waiter(object):
                 # transition to the failure state if an error
                 # response was received.
                 if 'Error' in response:
-                    # Transition to the failure state, which we can
-                    # just handle here by raising an exception.
-                    raise WaiterError(
-                        name=self.name,
-                        reason=response['Error'].get('Message', 'Unknown'))
+                    # This was a ClientError that was not handled in an
+                    # acceptor. Raise a ClientError again
+                    raise ClientError(
+                        error_response=response,
+                        operation_name=self.config.operation)
             if current_state == 'success':
                 logger.debug("Waiting complete, waiter matched the "
                              "success state.")


Test patch:
diff --git a/tests/unit/test_waiters.py b/tests/unit/test_waiters.py
index 3de747cdf..adef191ab 100644
--- a/tests/unit/test_waiters.py
+++ b/tests/unit/test_waiters.py
@@ -358,7 +358,7 @@ class TestWaitersObjects(unittest.TestCase):
             for_operation=operation_method
         )
         waiter = Waiter('MyWaiter', config, operation_method)
-        with self.assertRaises(WaiterError):
+        with self.assertRaises(ClientError):
             waiter.wait()
 
     def test_unspecified_errors_propagate_error_code(self):
@@ -378,7 +378,7 @@ class TestWaitersObjects(unittest.TestCase):
         )
         waiter = Waiter('MyWaiter', config, operation_method)
 
-        with self.assertRaisesRegexp(WaiterError, error_message):
+        with self.assertRaisesRegexp(ClientError, error_message):
             waiter.wait()
 
     def test_waiter_transitions_to_failure_state(self):