Blame SOURCES/python-rhsm-1.15.4-3-to-python-rhsm-1.15.4-4.patch

84ba10
diff --git a/python-rhsm.spec b/python-rhsm.spec
84ba10
index d2e00c1..d998de9 100644
84ba10
--- a/python-rhsm.spec
84ba10
+++ b/python-rhsm.spec
84ba10
@@ -13,7 +13,7 @@
84ba10
 
84ba10
 Name: python-rhsm
84ba10
 Version: 1.15.4
84ba10
-Release: 3%{?dist}
84ba10
+Release: 4%{?dist}
84ba10
 
84ba10
 Summary: A Python library to communicate with a Red Hat Unified Entitlement Platform
84ba10
 Group: Development/Libraries
84ba10
@@ -74,6 +74,10 @@ rm -rf %{buildroot}
84ba10
 %attr(644,root,root) %{_sysconfdir}/rhsm/ca/*.pem
84ba10
 
84ba10
 %changelog
84ba10
+* Wed Sep 02 2015 Chris Rog <crog@redhat.com> 1.15.4-4
84ba10
+- Adds RateLimitExceededException which is raised in response to 429 from the
84ba10
+  remote host (csnyder@redhat.com)
84ba10
+
84ba10
 * Wed Aug 12 2015 Chris Rog <crog@redhat.com> 1.15.4-3
84ba10
 - Add user-agent to rhsm requests. (alikins@redhat.com)
84ba10
 - 1247890: KeyErrors are now caught when checking manager capabilities
84ba10
diff --git a/rel-eng/packages/python-rhsm b/rel-eng/packages/python-rhsm
84ba10
index c370a05..6f475ed 100644
84ba10
--- a/rel-eng/packages/python-rhsm
84ba10
+++ b/rel-eng/packages/python-rhsm
84ba10
@@ -1 +1 @@
84ba10
-1.15.4-3 ./
84ba10
+1.15.4-4 ./
84ba10
diff --git a/src/rhsm/connection.py b/src/rhsm/connection.py
84ba10
index f5cbbdf..c4d0264 100644
84ba10
--- a/src/rhsm/connection.py
84ba10
+++ b/src/rhsm/connection.py
84ba10
@@ -115,7 +115,6 @@ def drift_check(utc_time_string, hours=1):
84ba10
     return drift
84ba10
 
84ba10
 
84ba10
-
84ba10
 class ConnectionException(Exception):
84ba10
     pass
84ba10
 
84ba10
@@ -137,9 +136,10 @@ class BadCertificateException(ConnectionException):
84ba10
 
84ba10
 class RestlibException(ConnectionException):
84ba10
 
84ba10
-    def __init__(self, code, msg=None):
84ba10
+    def __init__(self, code, msg=None, headers=None):
84ba10
         self.code = code
84ba10
         self.msg = msg or ""
84ba10
+        self.headers = headers or {}
84ba10
 
84ba10
     def __str__(self):
84ba10
         return self.msg
84ba10
@@ -192,6 +192,17 @@ class AuthenticationException(RemoteServerException):
84ba10
         return buf
84ba10
 
84ba10
 
84ba10
+class RateLimitExceededException(RestlibException):
84ba10
+    def __init__(self, code,
84ba10
+                 msg=None,
84ba10
+                 headers=None):
84ba10
+        super(RateLimitExceededException, self).__init__(code,
84ba10
+                                                         msg)
84ba10
+        self.headers = headers or {}
84ba10
+        self.msg = msg or ""
84ba10
+        self.retry_after = safe_int(self.headers.get('Retry-After'))
84ba10
+
84ba10
+
84ba10
 class UnauthorizedException(AuthenticationException):
84ba10
     prefix = "Unauthorized"
84ba10
 
84ba10
@@ -316,7 +327,8 @@ class ContentConnection(object):
84ba10
         response = conn.getresponse()
84ba10
         result = {
84ba10
             "content": response.read(),
84ba10
-            "status": response.status}
84ba10
+            "status": response.status,
84ba10
+            "headers": dict(response.getheaders())}
84ba10
 
84ba10
         return result
84ba10
 
84ba10
@@ -503,7 +515,6 @@ class Restlib(object):
84ba10
         else:
84ba10
             conn = httpslib.HTTPSConnection(self.host, self.ssl_port, ssl_context=context)
84ba10
 
84ba10
-
84ba10
         if info is not None:
84ba10
             body = json.dumps(info, default=json.encode)
84ba10
         else:
84ba10
@@ -534,6 +545,7 @@ class Restlib(object):
84ba10
         result = {
84ba10
             "content": response.read(),
84ba10
             "status": response.status,
84ba10
+            "headers": dict(response.getheaders())
84ba10
         }
84ba10
 
84ba10
         response_log = 'Response: status=' + str(result['status'])
84ba10
@@ -588,10 +600,15 @@ class Restlib(object):
84ba10
                 # had more meaningful exceptions. We've gotten a response from
84ba10
                 # the server that means something.
84ba10
 
84ba10
+                error_msg = self._parse_msg_from_error_response_body(parsed)
84ba10
+                if str(response['status']) in ['429']:
84ba10
+                    raise RateLimitExceededException(response['status'],
84ba10
+                                                     error_msg,
84ba10
+                                                     headers=response.get('headers'))
84ba10
+
84ba10
                 # FIXME: we can get here with a valid json response that
84ba10
                 # could be anything, we don't verify it anymore
84ba10
-                error_msg = self._parse_msg_from_error_response_body(parsed)
84ba10
-                raise RestlibException(response['status'], error_msg)
84ba10
+                raise RestlibException(response['status'], error_msg, response.get('headers'))
84ba10
             else:
84ba10
                 # This really needs an exception mapper too...
84ba10
                 if str(response['status']) in ["404", "410", "500", "502", "503", "504"]:
84ba10
@@ -606,6 +623,9 @@ class Restlib(object):
84ba10
                     raise ForbiddenException(response['status'],
84ba10
                                              request_type=request_type,
84ba10
                                              handler=handler)
84ba10
+                elif str(response['status']) in ['429']:
84ba10
+                    raise RateLimitExceededException(response['status'])
84ba10
+
84ba10
                 else:
84ba10
                     # unexpected with no valid content
84ba10
                     raise NetworkException(response['status'])
84ba10
diff --git a/test/unit/connection-tests.py b/test/unit/connection-tests.py
84ba10
index 8a0091d..a17b3ed 100644
84ba10
--- a/test/unit/connection-tests.py
84ba10
+++ b/test/unit/connection-tests.py
84ba10
@@ -20,7 +20,8 @@ import unittest
84ba10
 from rhsm.connection import UEPConnection, Restlib, ConnectionException, ConnectionSetupException, \
84ba10
         BadCertificateException, RestlibException, GoneException, NetworkException, \
84ba10
         RemoteServerException, drift_check, ExpiredIdentityCertException, UnauthorizedException, \
84ba10
-        ForbiddenException, AuthenticationException, set_default_socket_timeout_if_python_2_3
84ba10
+        ForbiddenException, AuthenticationException, set_default_socket_timeout_if_python_2_3, \
84ba10
+        RateLimitExceededException
84ba10
 
84ba10
 from mock import Mock, patch
84ba10
 from datetime import date
84ba10
@@ -157,9 +158,11 @@ class RestlibValidateResponseTests(unittest.TestCase):
84ba10
         self.request_type = "GET"
84ba10
         self.handler = "https://server/path"
84ba10
 
84ba10
-    def vr(self, status, content):
84ba10
+    def vr(self, status, content, headers=None):
84ba10
         response = {'status': status,
84ba10
                     'content': content}
84ba10
+        if headers:
84ba10
+            response['headers'] = headers
84ba10
         #print "response", response
84ba10
         self.restlib.validateResponse(response, self.request_type, self.handler)
84ba10
 
84ba10
@@ -347,6 +350,26 @@ class RestlibValidateResponseTests(unittest.TestCase):
84ba10
         else:
84ba10
             self.fail("Should have raised a GoneException")
84ba10
 
84ba10
+    def test_429_empty(self):
84ba10
+        try:
84ba10
+            self.vr("429", "")
84ba10
+        except RateLimitExceededException, e:
84ba10
+            self.assertEquals("429", e.code)
84ba10
+        else:
84ba10
+            self.fail("Should have raised a RateLimitExceededException")
84ba10
+
84ba10
+    def test_429_body(self):
84ba10
+        content = u'{"errors": ["TooFast"]}'
84ba10
+        headers = {'Retry-After': 20}
84ba10
+        try:
84ba10
+            self.vr("429", content, headers)
84ba10
+        except RateLimitExceededException, e:
84ba10
+            self.assertEquals(20, e.retry_after)
84ba10
+            self.assertEquals("TooFast", e.msg)
84ba10
+            self.assertEquals("429", e.code)
84ba10
+        else:
84ba10
+            self.fail("Should have raised a RateLimitExceededException")
84ba10
+
84ba10
     def test_500_empty(self):
84ba10
         try:
84ba10
             self.vr("500", "")