diff --git a/cinderclient/client.py b/cinderclient/client.py
index ee429ffb6..de4c59121 100644
--- a/cinderclient/client.py
+++ b/cinderclient/client.py
@@ -11,6 +11,10 @@ import httplib2
 import logging
 import os
 import urlparse
+try:
+    from eventlet import sleep
+except ImportError:
+    from time import sleep
 
 try:
     import json
@@ -42,7 +46,7 @@ class HTTPClient(httplib2.Http):
                  timeout=None, tenant_id=None, proxy_tenant_id=None,
                  proxy_token=None, region_name=None,
                  endpoint_type='publicURL', service_type=None,
-                 service_name=None, volume_service_name=None):
+                 service_name=None, volume_service_name=None, retries=None):
         super(HTTPClient, self).__init__(timeout=timeout)
         self.user = user
         self.password = password
@@ -55,6 +59,7 @@ class HTTPClient(httplib2.Http):
         self.service_type = service_type
         self.service_name = service_name
         self.volume_service_name = volume_service_name
+        self.retries = int(retries or 0)
 
         self.management_url = None
         self.auth_token = None
@@ -111,28 +116,47 @@ class HTTPClient(httplib2.Http):
         return resp, body
 
     def _cs_request(self, url, method, **kwargs):
-        if not self.management_url:
-            self.authenticate()
-
-        # Perform the request once. If we get a 401 back then it
-        # might be because the auth token expired, so try to
-        # re-authenticate and try again. If it still fails, bail.
-        try:
+        auth_attempts = 0
+        attempts = 0
+        backoff = 1
+        while True:
+            attempts += 1
+            if not self.management_url or not self.auth_token:
+                self.authenticate()
             kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
             if self.projectid:
                 kwargs['headers']['X-Auth-Project-Id'] = self.projectid
-
-            resp, body = self.request(self.management_url + url, method,
-                                      **kwargs)
-            return resp, body
-        except exceptions.Unauthorized, ex:
             try:
-                self.authenticate()
                 resp, body = self.request(self.management_url + url, method,
                                           **kwargs)
                 return resp, body
+            except exceptions.BadRequest as e:
+                if attempts > self.retries:
+                    raise
+                # Socket errors show up here (400) when
+                # force_exception_to_status_code = True
+                if e.message != 'n/a':
+                    raise
             except exceptions.Unauthorized:
-                raise ex
+                if auth_attempts > 0:
+                    raise
+                _logger.debug("Unauthorized, reauthenticating.")
+                self.management_url = self.auth_token = None
+                # First reauth. Discount this attempt.
+                attempts -= 1
+                auth_attempts += 1
+                continue
+            except exceptions.ClientException as e:
+                if attempts > self.retries:
+                    raise
+                if 500 <= e.code <= 599:
+                    pass
+                else:
+                    raise
+            _logger.debug("Failed attempt(%s of %s), retrying in %s seconds" %
+                          (attempts, self.retries, backoff))
+            sleep(backoff)
+            backoff *= 2
 
     def get(self, url, **kwargs):
         return self._cs_request(url, 'GET', **kwargs)
diff --git a/cinderclient/shell.py b/cinderclient/shell.py
index 0dfec5bcf..c24ef9826 100644
--- a/cinderclient/shell.py
+++ b/cinderclient/shell.py
@@ -169,6 +169,12 @@ class OpenStackCinderShell(object):
                             action='store_true',
                             help=argparse.SUPPRESS)
 
+        parser.add_argument('--retries',
+                            metavar='<retries>',
+                            type=int,
+                            default=0,
+                            help='Number of retries.')
+
         # FIXME(dtroyer): The args below are here for diablo compatibility,
         #                 remove them in folsum cycle
 
@@ -408,7 +414,8 @@ class OpenStackCinderShell(object):
                                 extensions=self.extensions,
                                 service_type=service_type,
                                 service_name=service_name,
-                                volume_service_name=volume_service_name)
+                                volume_service_name=volume_service_name,
+                                retries=options.retries)
 
         try:
             if not utils.isunauthenticated(args.func):
diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py
index e8e1207c2..7c81734f6 100644
--- a/cinderclient/v1/client.py
+++ b/cinderclient/v1/client.py
@@ -27,7 +27,7 @@ class Client(object):
                  proxy_tenant_id=None, proxy_token=None, region_name=None,
                  endpoint_type='publicURL', extensions=None,
                  service_type='volume', service_name=None,
-                 volume_service_name=None):
+                 volume_service_name=None, retries=None):
         # FIXME(comstud): Rename the api_key argument above when we
         # know it's not being used as keyword argument
         password = api_key
@@ -61,7 +61,8 @@ class Client(object):
             endpoint_type=endpoint_type,
             service_type=service_type,
             service_name=service_name,
-            volume_service_name=volume_service_name)
+            volume_service_name=volume_service_name,
+            retries=retries)
 
     def authenticate(self):
         """
diff --git a/tests/test_http.py b/tests/test_http.py
index 0b6ec6ff7..da514a08d 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -11,14 +11,14 @@ fake_body = '{"hi": "there"}'
 mock_request = mock.Mock(return_value=(fake_response, fake_body))
 
 
-def get_client():
+def get_client(retries=0):
     cl = client.HTTPClient("username", "password",
-                           "project_id", "auth_test")
+                           "project_id", "auth_test", retries=retries)
     return cl
 
 
-def get_authed_client():
-    cl = get_client()
+def get_authed_client(retries=0):
+    cl = get_client(retries=retries)
     cl.management_url = "http://example.com"
     cl.auth_token = "token"
     return cl
@@ -44,6 +44,111 @@ class ClientTest(utils.TestCase):
 
         test_get_call()
 
+    def test_get_reauth_0_retries(self):
+        cl = get_authed_client(retries=0)
+
+        bad_response = httplib2.Response({"status": 401})
+        bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
+        bad_request = mock.Mock(return_value=(bad_response, bad_body))
+        self.requests = [bad_request, mock_request]
+
+        def request(*args, **kwargs):
+            next_request = self.requests.pop(0)
+            return next_request(*args, **kwargs)
+
+        def reauth():
+            cl.management_url = "http://example.com"
+            cl.auth_token = "token"
+
+        @mock.patch.object(cl, 'authenticate', reauth)
+        @mock.patch.object(httplib2.Http, "request", request)
+        @mock.patch('time.time', mock.Mock(return_value=1234))
+        def test_get_call():
+            resp, body = cl.get("/hi")
+
+        test_get_call()
+        self.assertEqual(self.requests, [])
+
+    def test_get_retry_500(self):
+        cl = get_authed_client(retries=1)
+
+        bad_response = httplib2.Response({"status": 500})
+        bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
+        bad_request = mock.Mock(return_value=(bad_response, bad_body))
+        self.requests = [bad_request, mock_request]
+
+        def request(*args, **kwargs):
+            next_request = self.requests.pop(0)
+            return next_request(*args, **kwargs)
+
+        @mock.patch.object(httplib2.Http, "request", request)
+        @mock.patch('time.time', mock.Mock(return_value=1234))
+        def test_get_call():
+            resp, body = cl.get("/hi")
+
+        test_get_call()
+        self.assertEqual(self.requests, [])
+
+    def test_retry_limit(self):
+        cl = get_authed_client(retries=1)
+
+        bad_response = httplib2.Response({"status": 500})
+        bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
+        bad_request = mock.Mock(return_value=(bad_response, bad_body))
+        self.requests = [bad_request, bad_request, mock_request]
+
+        def request(*args, **kwargs):
+            next_request = self.requests.pop(0)
+            return next_request(*args, **kwargs)
+
+        @mock.patch.object(httplib2.Http, "request", request)
+        @mock.patch('time.time', mock.Mock(return_value=1234))
+        def test_get_call():
+            resp, body = cl.get("/hi")
+
+        self.assertRaises(exceptions.ClientException, test_get_call)
+        self.assertEqual(self.requests, [mock_request])
+
+    def test_get_no_retry_400(self):
+        cl = get_authed_client(retries=1)
+
+        bad_response = httplib2.Response({"status": 400})
+        bad_body = '{"error": {"message": "Bad!", "details": "Terrible!"}}'
+        bad_request = mock.Mock(return_value=(bad_response, bad_body))
+        self.requests = [bad_request, mock_request]
+
+        def request(*args, **kwargs):
+            next_request = self.requests.pop(0)
+            return next_request(*args, **kwargs)
+
+        @mock.patch.object(httplib2.Http, "request", request)
+        @mock.patch('time.time', mock.Mock(return_value=1234))
+        def test_get_call():
+            resp, body = cl.get("/hi")
+
+        self.assertRaises(exceptions.BadRequest, test_get_call)
+        self.assertEqual(self.requests, [mock_request])
+
+    def test_get_retry_400_socket(self):
+        cl = get_authed_client(retries=1)
+
+        bad_response = httplib2.Response({"status": 400})
+        bad_body = '{"error": {"message": "n/a", "details": "n/a"}}'
+        bad_request = mock.Mock(return_value=(bad_response, bad_body))
+        self.requests = [bad_request, mock_request]
+
+        def request(*args, **kwargs):
+            next_request = self.requests.pop(0)
+            return next_request(*args, **kwargs)
+
+        @mock.patch.object(httplib2.Http, "request", request)
+        @mock.patch('time.time', mock.Mock(return_value=1234))
+        def test_get_call():
+            resp, body = cl.get("/hi")
+
+        test_get_call()
+        self.assertEqual(self.requests, [])
+
     def test_post(self):
         cl = get_authed_client()