From f2dbe2e43716925f592db831d95fc5783abcecc9 Mon Sep 17 00:00:00 2001
From: Dean Troyer <dtroyer@gmail.com>
Date: Mon, 25 Nov 2013 13:39:30 -0600
Subject: [PATCH] Bring RESTApi closer to ithe imminent keystoneclient.Session

Prepare to use the (soon to be) common Session from keystoneclient
* Rework RESTApi to eventually be a subclass of keystoneclient.Session

Change-Id: I68e610f8b19a3f6267a93f7bf3de54a228be68aa
---
 openstackclient/common/restapi.py             | 299 +++++++++++++-----
 openstackclient/object/v1/lib/container.py    |  20 +-
 openstackclient/object/v1/lib/object.py       |  24 +-
 openstackclient/shell.py                      |   5 +-
 openstackclient/tests/common/test_restapi.py  | 138 ++++----
 .../tests/object/v1/lib/test_container.py     |  80 +++--
 .../tests/object/v1/lib/test_object.py        | 107 ++++---
 7 files changed, 435 insertions(+), 238 deletions(-)

diff --git a/openstackclient/common/restapi.py b/openstackclient/common/restapi.py
index a45c842607..1bb64fae03 100644
--- a/openstackclient/common/restapi.py
+++ b/openstackclient/common/restapi.py
@@ -25,14 +25,15 @@ except ImportError:
     from urllib import urlencode
 
 
+USER_AGENT = 'RAPI'
+
 _logger = logging.getLogger(__name__)
 
 
 class RESTApi(object):
-    """A REST api client that handles the interface from us to the server
+    """A REST API client that handles the interface from us to the server
 
-    RESTApi is an extension of a requests.Session that knows
-    how to do:
+    RESTApi is requests.Session wrapper that knows how to do:
     * JSON serialization/deserialization
     * log requests in 'curl' format
     * basic API boilerplate for create/delete/list/set/show verbs
@@ -46,26 +47,49 @@ class RESTApi(object):
     it communicates with, such as the available endpoints, API versions, etc.
     """
 
-    USER_AGENT = 'RAPI'
-
     def __init__(
         self,
-        os_auth=None,
+        session=None,
+        auth_header=None,
         user_agent=USER_AGENT,
-        debug=None,
         verify=True,
-        **kwargs
+        logger=None,
+        debug=None,
     ):
-        self.set_auth(os_auth)
+        """Construct a new REST client
+
+        :param object session: A Session object to be used for
+                               communicating with the identity service.
+        :param string auth_header: A token from an initialized auth_reference
+                                   to be used in the X-Auth-Token header
+        :param string user_agent: Set the User-Agent header in the requests
+        :param boolean/string verify: If ``True``, the SSL cert will be
+                                      verified. A CA_BUNDLE path can also be
+                                      provided.
+        :param logging.Logger logger: A logger to output to. (optional)
+        :param boolean debug: Enables debug logging of all request and
+                              responses to identity service.
+                              default False (optional)
+        """
+
+        self.set_auth(auth_header)
         self.debug = debug
-        self.session = requests.Session(**kwargs)
 
-        self.set_header('User-Agent', user_agent)
-        self.set_header('Content-Type', 'application/json')
+        if not session:
+            # We create a default session object
+            session = requests.Session()
+        self.session = session
+        self.session.verify = verify
+        self.session.user_agent = user_agent
 
-    def set_auth(self, os_auth):
+        if logger:
+            self.logger = logger
+        else:
+            self.logger = _logger
+
+    def set_auth(self, auth_header):
         """Sets the current auth blob"""
-        self.os_auth = os_auth
+        self.auth_header = auth_header
 
     def set_header(self, header, content):
         """Sets passed in headers into the session headers
@@ -78,37 +102,154 @@ class RESTApi(object):
             self.session.headers[header] = content
 
     def request(self, method, url, **kwargs):
-        if self.os_auth:
-            self.session.headers.setdefault('X-Auth-Token', self.os_auth)
-        if 'data' in kwargs and isinstance(kwargs['data'], type({})):
-            kwargs['data'] = json.dumps(kwargs['data'])
-        log_request(method, url, headers=self.session.headers, **kwargs)
+        """Make an authenticated (if token available) request
+
+        :param method: Request HTTP method
+        :param url: Request URL
+        :param data: Request body
+        :param json: Request body to be encoded as JSON
+                     Overwrites ``data`` argument if present
+        """
+
+        kwargs.setdefault('headers', {})
+        if self.auth_header:
+            kwargs['headers']['X-Auth-Token'] = self.auth_header
+
+        if 'json' in kwargs and isinstance(kwargs['json'], type({})):
+            kwargs['data'] = json.dumps(kwargs.pop('json'))
+            kwargs['headers']['Content-Type'] = 'application/json'
+
+        kwargs.setdefault('allow_redirects', True)
+
+        if self.debug:
+            self._log_request(method, url, **kwargs)
+
         response = self.session.request(method, url, **kwargs)
-        log_response(response)
+
+        if self.debug:
+            self._log_response(response)
+
         return self._error_handler(response)
 
+    def _error_handler(self, response):
+        if response.status_code < 200 or response.status_code >= 300:
+            self.logger.debug(
+                "ERROR: %s",
+                response.text,
+            )
+            response.raise_for_status()
+        return response
+
+    # Convenience methods to mimic the ones provided by requests.Session
+
+    def delete(self, url, **kwargs):
+        """Send a DELETE request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('DELETE', url, **kwargs)
+
+    def get(self, url, **kwargs):
+        """Send a GET request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('GET', url, **kwargs)
+
+    def head(self, url, **kwargs):
+        """Send a HEAD request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        kwargs.setdefault('allow_redirects', False)
+        return self.request('HEAD', url, **kwargs)
+
+    def options(self, url, **kwargs):
+        """Send an OPTIONS request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('OPTIONS', url, **kwargs)
+
+    def patch(self, url, data=None, json=None, **kwargs):
+        """Send a PUT request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param data: Request body
+        :param json: Request body to be encoded as JSON
+                     Overwrites ``data`` argument if present
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('PATCH', url, data=data, json=json, **kwargs)
+
+    def post(self, url, data=None, json=None, **kwargs):
+        """Send a POST request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param data: Request body
+        :param json: Request body to be encoded as JSON
+                     Overwrites ``data`` argument if present
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('POST', url, data=data, json=json, **kwargs)
+
+    def put(self, url, data=None, json=None, **kwargs):
+        """Send a PUT request. Returns :class:`requests.Response` object.
+
+        :param url: Request URL
+        :param data: Request body
+        :param json: Request body to be encoded as JSON
+                     Overwrites ``data`` argument if present
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        return self.request('PUT', url, data=data, json=json, **kwargs)
+
+    # Command verb methods
+
     def create(self, url, data=None, response_key=None, **kwargs):
-        response = self.request('POST', url, data=data, **kwargs)
+        """Create a new object via a POST request
+
+        :param url: Request URL
+        :param data: Request body, wil be JSON encoded
+        :param response_key: Dict key in response body to extract
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        response = self.request('POST', url, json=data, **kwargs)
         if response_key:
             return response.json()[response_key]
         else:
             return response.json()
 
-        #with self.completion_cache('human_id', self.resource_class, mode="a"):
-        #    with self.completion_cache('uuid', self.resource_class, mode="a"):
-        #        return self.resource_class(self, body[response_key])
-
-    def delete(self, url):
-        self.request('DELETE', url)
-
     def list(self, url, data=None, response_key=None, **kwargs):
+        """Retrieve a list of objects via a GET or POST request
+
+        :param url: Request URL
+        :param data: Request body, will be JSON encoded
+        :param response_key: Dict key in response body to extract
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
         if data:
-            response = self.request('POST', url, data=data, **kwargs)
+            response = self.request('POST', url, json=data, **kwargs)
         else:
-            kwargs.setdefault('allow_redirects', True)
             response = self.request('GET', url, **kwargs)
 
-        return response.json()[response_key]
+        if response_key:
+            return response.json()[response_key]
+        else:
+            return response.json()
 
         ###hack this for keystone!!!
         #data = body[response_key]
@@ -120,70 +261,70 @@ class RESTApi(object):
         #    except KeyError:
         #        pass
 
-        #with self.completion_cache('human_id', obj_class, mode="w"):
-        #    with self.completion_cache('uuid', obj_class, mode="w"):
-        #        return [obj_class(self, res, loaded=True)
-        #                for res in data if res]
-
     def set(self, url, data=None, response_key=None, **kwargs):
-        response = self.request('PUT', url, data=data)
+        """Update an object via a PUT request
+
+        :param url: Request URL
+        :param data: Request body
+        :param json: Request body to be encoded as JSON
+                     Overwrites ``data`` argument if present
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
+        response = self.request('PUT', url, json=data)
         if data:
             if response_key:
                 return response.json()[response_key]
             else:
                 return response.json()
         else:
+            # Nothing to do here
             return None
 
     def show(self, url, response_key=None, **kwargs):
+        """Retrieve a single object via a GET request
+
+        :param url: Request URL
+        :param response_key: Dict key in response body to extract
+        :param \*\*kwargs: Optional arguments passed to ``request``
+        """
+
         response = self.request('GET', url, **kwargs)
         if response_key:
             return response.json()[response_key]
         else:
             return response.json()
 
-    def _error_handler(self, response):
-        if response.status_code < 200 or response.status_code >= 300:
-            _logger.debug(
-                "ERROR: %s",
+    def _log_request(self, method, url, **kwargs):
+        if 'params' in kwargs and kwargs['params'] != {}:
+            url += '?' + urlencode(kwargs['params'])
+
+        string_parts = [
+            "curl -i",
+            "-X '%s'" % method,
+            "'%s'" % url,
+        ]
+
+        for element in kwargs['headers']:
+            header = " -H '%s: %s'" % (element, kwargs['headers'][element])
+            string_parts.append(header)
+
+        self.logger.debug("REQ: %s" % " ".join(string_parts))
+        if 'data' in kwargs:
+            self.logger.debug("  REQ BODY: %r\n" % (kwargs['data']))
+
+    def _log_response(self, response):
+        self.logger.debug(
+            "RESP: [%s] %r\n",
+            response.status_code,
+            response.headers,
+        )
+        if response._content_consumed:
+            self.logger.debug(
+                "  RESP BODY: %s\n",
                 response.text,
             )
-            response.raise_for_status()
-        return response
-
-
-def log_request(method, url, **kwargs):
-    # put in an early exit if debugging is not enabled?
-    if 'params' in kwargs and kwargs['params'] != {}:
-        url += '?' + urlencode(kwargs['params'])
-
-    string_parts = [
-        "curl -i",
-        "-X '%s'" % method,
-        "'%s'" % url,
-    ]
-
-    for element in kwargs['headers']:
-        header = " -H '%s: %s'" % (element, kwargs['headers'][element])
-        string_parts.append(header)
-
-    _logger.debug("REQ: %s" % " ".join(string_parts))
-    if 'data' in kwargs:
-        _logger.debug("REQ BODY: %s\n" % (kwargs['data']))
-
-
-def log_response(response):
-    _logger.debug(
-        "RESP: [%s] %s\n",
-        response.status_code,
-        response.headers,
-    )
-    if response._content_consumed:
-        _logger.debug(
-            "RESP BODY: %s\n",
-            response.text,
+        self.logger.debug(
+            "  encoding: %s",
+            response.encoding,
         )
-    _logger.debug(
-        "encoding: %s",
-        response.encoding,
-    )
diff --git a/openstackclient/object/v1/lib/container.py b/openstackclient/object/v1/lib/container.py
index 5103d9d4ae..0bae23493f 100644
--- a/openstackclient/object/v1/lib/container.py
+++ b/openstackclient/object/v1/lib/container.py
@@ -67,19 +67,18 @@ def list_containers(
                 data.extend(listing)
         return data
 
-    object_url = url
-    query = "format=json"
+    params = {
+        'format': 'json',
+    }
     if marker:
-        query += '&marker=%s' % marker
+        params['marker'] = marker
     if limit:
-        query += '&limit=%d' % limit
+        params['limit'] = limit
     if end_marker:
-        query += '&end_marker=%s' % end_marker
+        params['end_marker'] = end_marker
     if prefix:
-        query += '&prefix=%s' % prefix
-    url = "%s?%s" % (object_url, query)
-    response = api.request('GET', url)
-    return response.json()
+        params['prefix'] = prefix
+    return api.list(url, params=params)
 
 
 def show_container(
@@ -95,9 +94,8 @@ def show_container(
     :returns: dict of returned headers
     """
 
-    object_url = "%s/%s" % (url, container)
+    response = api.head("%s/%s" % (url, container))
     url_parts = urlparse(url)
-    response = api.request('HEAD', object_url)
     data = {
         'account': url_parts.path.split('/')[-1],
         'container': container,
diff --git a/openstackclient/object/v1/lib/object.py b/openstackclient/object/v1/lib/object.py
index 8ad5e5a51a..6f9c9d63d3 100644
--- a/openstackclient/object/v1/lib/object.py
+++ b/openstackclient/object/v1/lib/object.py
@@ -86,22 +86,23 @@ def list_objects(
         return data
 
     object_url = url
-    query = "format=json"
+    params = {
+        'format': 'json',
+    }
     if marker:
-        query += '&marker=%s' % marker
+        params['marker'] = marker
     if limit:
-        query += '&limit=%d' % limit
+        params['limit'] = limit
     if end_marker:
-        query += '&end_marker=%s' % end_marker
+        params['end_marker'] = end_marker
     if delimiter:
-        query += '&delimiter=%s' % delimiter
+        params['delimiter'] = delimiter
     if prefix:
-        query += '&prefix=%s' % prefix
+        params['prefix'] = prefix
     if path:
-        query += '&path=%s' % path
-    url = "%s/%s?%s" % (object_url, container, query)
-    response = api.request('GET', url)
-    return response.json()
+        params['path'] = path
+    url = "%s/%s" % (object_url, container)
+    return api.list(url, params=params)
 
 
 def show_object(
@@ -118,9 +119,8 @@ def show_object(
     :returns: dict of object properties
     """
 
-    object_url = "%s/%s/%s" % (url, container, obj)
+    response = api.head("%s/%s/%s" % (url, container, obj))
     url_parts = urlparse(url)
-    response = api.request('HEAD', object_url)
     data = {
         'account': url_parts.path.split('/')[-1],
         'container': container,
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index f8a47ca664..b508d37903 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -405,7 +405,10 @@ class OpenStackShell(app.App):
             self.verify = self.options.os_cacert
         else:
             self.verify = not self.options.insecure
-        self.restapi = restapi.RESTApi(verify=self.verify)
+        self.restapi = restapi.RESTApi(
+            verify=self.verify,
+            debug=self.options.debug,
+        )
 
     def prepare_to_run_command(self, cmd):
         """Set up auth and API versions"""
diff --git a/openstackclient/tests/common/test_restapi.py b/openstackclient/tests/common/test_restapi.py
index 4b83ffa460..c1e02fcbcf 100644
--- a/openstackclient/tests/common/test_restapi.py
+++ b/openstackclient/tests/common/test_restapi.py
@@ -23,6 +23,8 @@ import requests
 from openstackclient.common import restapi
 from openstackclient.tests import utils
 
+fake_user_agent = 'test_rapi'
+
 fake_auth = '11223344556677889900'
 fake_url = 'http://gopher.com'
 fake_key = 'gopher'
@@ -47,6 +49,9 @@ fake_gopher_list = {
             fake_gopher_tosh,
         ]
 }
+fake_headers = {
+    'User-Agent': fake_user_agent,
+}
 
 
 class FakeResponse(requests.Response):
@@ -68,11 +73,15 @@ class TestRESTApi(utils.TestCase):
             request=mock.MagicMock(return_value=resp),
         )
 
-        api = restapi.RESTApi()
+        api = restapi.RESTApi(
+            user_agent=fake_user_agent,
+        )
         gopher = api.request('GET', fake_url)
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers={},
+            allow_redirects=True,
         )
         self.assertEqual(gopher.status_code, 200)
         self.assertEqual(gopher.json(), fake_gopher_single)
@@ -83,11 +92,15 @@ class TestRESTApi(utils.TestCase):
             request=mock.MagicMock(return_value=resp),
         )
 
-        api = restapi.RESTApi()
+        api = restapi.RESTApi(
+            user_agent=fake_user_agent,
+        )
         gopher = api.request('GET', fake_url)
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers={},
+            allow_redirects=True,
         )
         self.assertEqual(gopher.status_code, 300)
         self.assertEqual(gopher.json(), fake_gopher_single)
@@ -98,11 +111,15 @@ class TestRESTApi(utils.TestCase):
             request=mock.MagicMock(return_value=resp),
         )
 
-        api = restapi.RESTApi()
+        api = restapi.RESTApi(
+            user_agent=fake_user_agent,
+        )
         self.assertRaises(requests.HTTPError, api.request, 'GET', fake_url)
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers={},
+            allow_redirects=True,
         )
 
     def test_request_get_auth(self, session_mock):
@@ -112,67 +129,68 @@ class TestRESTApi(utils.TestCase):
             headers=mock.MagicMock(return_value={}),
         )
 
-        api = restapi.RESTApi(os_auth=fake_auth)
-        gopher = api.request('GET', fake_url)
-        session_mock.return_value.headers.setdefault.assert_called_with(
-            'X-Auth-Token',
-            fake_auth,
+        api = restapi.RESTApi(
+            auth_header=fake_auth,
+            user_agent=fake_user_agent,
         )
+        gopher = api.request('GET', fake_url)
+        #session_mock.return_value.headers.setdefault.assert_called_with(
+        #    'X-Auth-Token',
+        #    fake_auth,
+        #)
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers={
+                'X-Auth-Token': fake_auth,
+            },
+            allow_redirects=True,
         )
         self.assertEqual(gopher.json(), fake_gopher_single)
 
-    def test_request_get_header(self, session_mock):
-        resp = FakeResponse(data=fake_gopher_single)
-        session_mock.return_value = mock.MagicMock(
-            request=mock.MagicMock(return_value=resp),
-            headers=mock.MagicMock(return_value={}),
-        )
-
-        api = restapi.RESTApi(user_agent='fake_agent')
-        api.set_header('X-Fake-Header', 'wb')
-        gopher = api.request('GET', fake_url)
-        session_mock.return_value.headers.__setitem__.assert_any_call(
-            'Content-Type',
-            'application/json',
-        )
-        session_mock.return_value.headers.__setitem__.assert_any_call(
-            'User-Agent',
-            'fake_agent',
-        )
-        session_mock.return_value.headers.__setitem__.assert_any_call(
-            'X-Fake-Header',
-            'wb',
-        )
-        session_mock.return_value.request.assert_called_with(
-            'GET',
-            fake_url,
-        )
-        self.assertEqual(gopher.json(), fake_gopher_single)
-
-        api.set_header('X-Fake-Header', None)
-        session_mock.return_value.headers.__delitem__.assert_any_call(
-            'X-Fake-Header',
-        )
-
     def test_request_post(self, session_mock):
         resp = FakeResponse(data=fake_gopher_single)
         session_mock.return_value = mock.MagicMock(
             request=mock.MagicMock(return_value=resp),
         )
 
-        api = restapi.RESTApi()
+        api = restapi.RESTApi(
+            user_agent=fake_user_agent,
+        )
         data = fake_gopher_tosh
-        gopher = api.request('POST', fake_url, data=data)
+        gopher = api.request('POST', fake_url, json=data)
         session_mock.return_value.request.assert_called_with(
             'POST',
             fake_url,
+            headers={
+                'Content-Type': 'application/json',
+            },
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher.json(), fake_gopher_single)
 
+    # Methods
+    # TODO(dtroyer): add the other method methods
+
+    def test_delete(self, session_mock):
+        resp = FakeResponse(status_code=200, data=None)
+        session_mock.return_value = mock.MagicMock(
+            request=mock.MagicMock(return_value=resp),
+        )
+
+        api = restapi.RESTApi()
+        gopher = api.delete(fake_url)
+        session_mock.return_value.request.assert_called_with(
+            'DELETE',
+            fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
+        )
+        self.assertEqual(gopher.status_code, 200)
+
+    # Commands
+
     def test_create(self, session_mock):
         resp = FakeResponse(data=fake_gopher_single)
         session_mock.return_value = mock.MagicMock(
@@ -187,6 +205,8 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'POST',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher, fake_gopher_single)
@@ -196,24 +216,12 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'POST',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher, fake_gopher_mac)
 
-    def test_delete(self, session_mock):
-        resp = FakeResponse(data=None)
-        session_mock.return_value = mock.MagicMock(
-            request=mock.MagicMock(return_value=resp),
-        )
-
-        api = restapi.RESTApi()
-        gopher = api.delete(fake_url)
-        session_mock.return_value.request.assert_called_with(
-            'DELETE',
-            fake_url,
-        )
-        self.assertEqual(gopher, None)
-
     def test_list(self, session_mock):
         resp = FakeResponse(data=fake_gopher_list)
         session_mock.return_value = mock.MagicMock(
@@ -226,6 +234,7 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers=mock.ANY,
             allow_redirects=True,
         )
         self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh])
@@ -237,6 +246,8 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'POST',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh])
@@ -248,6 +259,7 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers=mock.ANY,
             allow_redirects=True,
             params=params,
         )
@@ -270,7 +282,9 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'PUT',
             fake_url,
-            data=None,
+            headers=mock.ANY,
+            allow_redirects=True,
+            json=None,
         )
         self.assertEqual(gopher, None)
 
@@ -279,6 +293,8 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'PUT',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher, fake_gopher_single)
@@ -291,6 +307,8 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'PUT',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
             data=json.dumps(data),
         )
         self.assertEqual(gopher, fake_gopher_mac)
@@ -308,6 +326,8 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
         )
         self.assertEqual(gopher, fake_gopher_single)
 
@@ -316,5 +336,7 @@ class TestRESTApi(utils.TestCase):
         session_mock.return_value.request.assert_called_with(
             'GET',
             fake_url,
+            headers=mock.ANY,
+            allow_redirects=True,
         )
         self.assertEqual(gopher, fake_gopher_mac)
diff --git a/openstackclient/tests/object/v1/lib/test_container.py b/openstackclient/tests/object/v1/lib/test_container.py
index c3fdea72b0..f7355592dd 100644
--- a/openstackclient/tests/object/v1/lib/test_container.py
+++ b/openstackclient/tests/object/v1/lib/test_container.py
@@ -46,7 +46,7 @@ class TestContainerList(TestContainer):
 
     def test_container_list_no_options(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -54,15 +54,17 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_container_list_marker(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -71,15 +73,18 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json&marker=next',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+                'marker': 'next',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_container_list_limit(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -88,15 +93,18 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json&limit=5',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+                'limit': 5,
+            }
         )
         self.assertEqual(data, resp)
 
     def test_container_list_end_marker(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -105,15 +113,18 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json&end_marker=last',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+                'end_marker': 'last',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_container_list_prefix(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -122,25 +133,26 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json&prefix=foo/',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+                'prefix': 'foo/',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_container_list_full_listing(self):
 
         def side_effect(*args, **kwargs):
-            rv = self.app.restapi.request.return_value
-            self.app.restapi.request.return_value = restapi.FakeResponse(
-                data=[],
-            )
-            self.app.restapi.request.side_effect = None
+            rv = self.app.restapi.list.return_value
+            self.app.restapi.list.return_value = []
+            self.app.restapi.list.side_effect = None
             return rv
 
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
-        self.app.restapi.request.side_effect = side_effect
+        self.app.restapi.list.return_value = resp
+        self.app.restapi.list.side_effect = side_effect
 
         data = lib_container.list_containers(
             self.app.restapi,
@@ -149,9 +161,12 @@ class TestContainerList(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '?format=json&marker=is-name',
+        self.app.restapi.list.assert_called_with(
+            fake_url,
+            params={
+                'format': 'json',
+                'marker': 'is-name',
+            }
         )
         self.assertEqual(data, resp)
 
@@ -163,7 +178,7 @@ class TestContainerShow(TestContainer):
             'x-container-object-count': 1,
             'x-container-bytes-used': 577,
         }
-        self.app.restapi.request.return_value = \
+        self.app.restapi.head.return_value = \
             restapi.FakeResponse(headers=resp)
 
         data = lib_container.show_container(
@@ -173,8 +188,7 @@ class TestContainerShow(TestContainer):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'HEAD',
+        self.app.restapi.head.assert_called_with(
             fake_url + '/is-name',
         )
 
diff --git a/openstackclient/tests/object/v1/lib/test_object.py b/openstackclient/tests/object/v1/lib/test_object.py
index ef93877aea..064efb53ed 100644
--- a/openstackclient/tests/object/v1/lib/test_object.py
+++ b/openstackclient/tests/object/v1/lib/test_object.py
@@ -47,7 +47,7 @@ class TestObjectListObjects(TestObject):
 
     def test_list_objects_no_options(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -56,15 +56,17 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_marker(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -74,15 +76,18 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&marker=next',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'marker': 'next',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_limit(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -92,15 +97,18 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&limit=5',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'limit': 5,
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_end_marker(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -110,15 +118,18 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&end_marker=last',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'end_marker': 'last',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_delimiter(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -131,15 +142,18 @@ class TestObjectListObjects(TestObject):
         # NOTE(dtroyer): requests handles the URL encoding and we're
         #                mocking that so use the otherwise-not-legal
         #                pipe '|' char in the response.
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&delimiter=|',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'delimiter': '|',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_prefix(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -149,15 +163,18 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&prefix=foo/',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'prefix': 'foo/',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_path(self):
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
+        self.app.restapi.list.return_value = resp
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -167,25 +184,26 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&path=next',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'path': 'next',
+            }
         )
         self.assertEqual(data, resp)
 
     def test_list_objects_full_listing(self):
 
         def side_effect(*args, **kwargs):
-            rv = self.app.restapi.request.return_value
-            self.app.restapi.request.return_value = restapi.FakeResponse(
-                data=[],
-            )
-            self.app.restapi.request.side_effect = None
+            rv = self.app.restapi.list.return_value
+            self.app.restapi.list.return_value = []
+            self.app.restapi.list.side_effect = None
             return rv
 
         resp = [{'name': 'is-name'}]
-        self.app.restapi.request.return_value = restapi.FakeResponse(data=resp)
-        self.app.restapi.request.side_effect = side_effect
+        self.app.restapi.list.return_value = resp
+        self.app.restapi.list.side_effect = side_effect
 
         data = lib_object.list_objects(
             self.app.restapi,
@@ -195,9 +213,12 @@ class TestObjectListObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'GET',
-            fake_url + '/' + fake_container + '?format=json&marker=is-name',
+        self.app.restapi.list.assert_called_with(
+            fake_url + '/' + fake_container,
+            params={
+                'format': 'json',
+                'marker': 'is-name',
+            }
         )
         self.assertEqual(data, resp)
 
@@ -208,7 +229,7 @@ class TestObjectShowObjects(TestObject):
         resp = {
             'content-type': 'text/alpha',
         }
-        self.app.restapi.request.return_value = \
+        self.app.restapi.head.return_value = \
             restapi.FakeResponse(headers=resp)
 
         data = lib_object.show_object(
@@ -219,8 +240,7 @@ class TestObjectShowObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'HEAD',
+        self.app.restapi.head.assert_called_with(
             fake_url + '/%s/%s' % (fake_container, fake_object),
         )
 
@@ -242,7 +262,7 @@ class TestObjectShowObjects(TestObject):
             'x-object-meta-wife': 'Wilma',
             'x-tra-header': 'yabba-dabba-do',
         }
-        self.app.restapi.request.return_value = \
+        self.app.restapi.head.return_value = \
             restapi.FakeResponse(headers=resp)
 
         data = lib_object.show_object(
@@ -253,8 +273,7 @@ class TestObjectShowObjects(TestObject):
         )
 
         # Check expected values
-        self.app.restapi.request.assert_called_with(
-            'HEAD',
+        self.app.restapi.head.assert_called_with(
             fake_url + '/%s/%s' % (fake_container, fake_object),
         )