Replace httplib2 with httplib as http driver
* This allows us to send truly chunked responses to users * Handle bad connection url schemes with a new InvalidEndpoint exception * Fixes bug 1023240 Change-Id: I34500987f51d4e0c6e1f89ecf93853de3fcbb1c3
This commit is contained in:
		@@ -3,12 +3,11 @@ OpenStack Client interface. Handles the REST calls and responses.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import httplib
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import StringIO
 | 
			
		||||
import urlparse
 | 
			
		||||
 | 
			
		||||
import httplib2
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import json
 | 
			
		||||
@@ -29,18 +28,26 @@ USER_AGENT = 'python-glanceclient'
 | 
			
		||||
CHUNKSIZE = 1024 * 64  # 64kB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(httplib2.Http):
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint, token=None, timeout=600, insecure=False):
 | 
			
		||||
        super(HTTPClient, self).__init__(timeout=timeout)
 | 
			
		||||
        self.endpoint = endpoint
 | 
			
		||||
        parts = urlparse.urlparse(endpoint)
 | 
			
		||||
        self.connection_class = self.get_connection_class(parts.scheme)
 | 
			
		||||
        self.endpoint = (parts.hostname, parts.port)
 | 
			
		||||
        self.auth_token = token
 | 
			
		||||
 | 
			
		||||
        # httplib2 overrides
 | 
			
		||||
        self.force_exception_to_status_code = True
 | 
			
		||||
        self.disable_ssl_certificate_validation = insecure
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_connection_class(scheme):
 | 
			
		||||
        try:
 | 
			
		||||
            return getattr(httplib, '%sConnection' % scheme.upper())
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            msg = 'Unsupported scheme: %s' % scheme
 | 
			
		||||
            raise exc.InvalidEndpoint(msg)
 | 
			
		||||
 | 
			
		||||
    def http_log(self, args, kwargs, resp, body):
 | 
			
		||||
    def get_connection(self):
 | 
			
		||||
        return self.connection_class(*self.endpoint)
 | 
			
		||||
 | 
			
		||||
    def http_log(self, args, kwargs, resp):
 | 
			
		||||
        if os.environ.get('GLANCECLIENT_DEBUG', False):
 | 
			
		||||
            ch = logging.StreamHandler()
 | 
			
		||||
            logger.setLevel(logging.DEBUG)
 | 
			
		||||
@@ -64,37 +71,34 @@ class HTTPClient(httplib2.Http):
 | 
			
		||||
            logger.debug("REQ BODY (RAW): %s\n" % (kwargs['raw_body']))
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            logger.debug("REQ BODY: %s\n" % (kwargs['body']))
 | 
			
		||||
        logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
 | 
			
		||||
        logger.debug("RESP: %s", resp)
 | 
			
		||||
 | 
			
		||||
    def _http_request(self, url, method, **kwargs):
 | 
			
		||||
        """ Send an http request with the specified characteristics.
 | 
			
		||||
 | 
			
		||||
        Wrapper around httplib2.Http.request to handle tasks such as
 | 
			
		||||
        setting headers, JSON encoding/decoding, and error handling.
 | 
			
		||||
        Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
 | 
			
		||||
        as setting headers and error handling.
 | 
			
		||||
        """
 | 
			
		||||
        url = self.endpoint + url
 | 
			
		||||
 | 
			
		||||
        # Copy the kwargs so we can reuse the original in case of redirects
 | 
			
		||||
        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
 | 
			
		||||
        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
 | 
			
		||||
        if self.auth_token:
 | 
			
		||||
            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
 | 
			
		||||
 | 
			
		||||
        resp, body = super(HTTPClient, self).request(url, method, **kwargs)
 | 
			
		||||
        self.http_log((url, method,), kwargs, resp, body)
 | 
			
		||||
        conn = self.get_connection()
 | 
			
		||||
        conn.request(method, url, **kwargs)
 | 
			
		||||
        resp = conn.getresponse()
 | 
			
		||||
 | 
			
		||||
        self.http_log((url, method,), kwargs, resp)
 | 
			
		||||
 | 
			
		||||
        if 400 <= resp.status < 600:
 | 
			
		||||
            logger.exception("Request returned failure status.")
 | 
			
		||||
            raise exc.from_response(resp, body)
 | 
			
		||||
            raise exc.from_response(resp)
 | 
			
		||||
        elif resp.status in (301, 302, 305):
 | 
			
		||||
            # Redirected. Reissue the request to the new location.
 | 
			
		||||
            return self._http_request(resp['location'], method, **kwargs)
 | 
			
		||||
 | 
			
		||||
        #NOTE(bcwaldon): body has been loaded to a string at this point,
 | 
			
		||||
        # but we want to move to a world where it can be read from a
 | 
			
		||||
        # socket-level cache. This is here until we can do that.
 | 
			
		||||
        body_iter = ResponseBodyIterator(StringIO.StringIO(body))
 | 
			
		||||
 | 
			
		||||
        body_iter = ResponseBodyIterator(resp)
 | 
			
		||||
        return resp, body_iter
 | 
			
		||||
 | 
			
		||||
    def json_request(self, method, url, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,11 @@ class SchemaNotFound(KeyError):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidEndpoint(ValueError):
 | 
			
		||||
    """The provided endpoint could not be used"""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientException(Exception):
 | 
			
		||||
    """
 | 
			
		||||
    The base exception class for all exceptions this library raises.
 | 
			
		||||
@@ -104,28 +109,7 @@ _code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
 | 
			
		||||
                   Forbidden, NotFound, OverLimit, HTTPNotImplemented])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_response(response, body):
 | 
			
		||||
    """
 | 
			
		||||
    Return an instance of an ClientException or subclass
 | 
			
		||||
    based on an httplib2 response.
 | 
			
		||||
 | 
			
		||||
    Usage::
 | 
			
		||||
 | 
			
		||||
        resp, body = http.request(...)
 | 
			
		||||
        if resp.status != 200:
 | 
			
		||||
            raise exception_from_response(resp, body)
 | 
			
		||||
    """
 | 
			
		||||
def from_response(response):
 | 
			
		||||
    """Return an instance of an ClientException based on httplib response."""
 | 
			
		||||
    cls = _code_map.get(response.status, ClientException)
 | 
			
		||||
    if body:
 | 
			
		||||
        if hasattr(body, 'keys'):
 | 
			
		||||
            error = body[body.keys()[0]]
 | 
			
		||||
            message = error.get('message', None)
 | 
			
		||||
            details = error.get('details', None)
 | 
			
		||||
        else:
 | 
			
		||||
            # If we didn't get back a properly formed error message we
 | 
			
		||||
            # probably couldn't communicate with Keystone at all.
 | 
			
		||||
            message = "Unable to communicate with image service: %s." % body
 | 
			
		||||
            details = None
 | 
			
		||||
        return cls(code=response.status, message=message, details=details)
 | 
			
		||||
    else:
 | 
			
		||||
    return cls(code=response.status)
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ Command-line interface to the OpenStack Images API.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import httplib2
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
@@ -234,10 +233,6 @@ class OpenStackImagesShell(object):
 | 
			
		||||
        # Parse args again and call whatever callback was selected
 | 
			
		||||
        args = subcommand_parser.parse_args(argv)
 | 
			
		||||
 | 
			
		||||
        # Deal with global arguments
 | 
			
		||||
        if args.debug:
 | 
			
		||||
            httplib2.debuglevel = 1
 | 
			
		||||
 | 
			
		||||
        # Short-circuit and deal with help command right away.
 | 
			
		||||
        if args.func == self.do_help:
 | 
			
		||||
            self.do_help(args)
 | 
			
		||||
@@ -315,8 +310,5 @@ def main():
 | 
			
		||||
        OpenStackImagesShell().main(sys.argv[1:])
 | 
			
		||||
 | 
			
		||||
    except Exception, e:
 | 
			
		||||
        if httplib2.debuglevel == 1:
 | 
			
		||||
            raise
 | 
			
		||||
        else:
 | 
			
		||||
        print >> sys.stderr, e
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ class ImageManager(base.Manager):
 | 
			
		||||
        :rtype: :class:`Image`
 | 
			
		||||
        """
 | 
			
		||||
        resp, body = self.api.raw_request('HEAD', '/v1/images/%s' % image_id)
 | 
			
		||||
        meta = self._image_meta_from_headers(resp)
 | 
			
		||||
        meta = self._image_meta_from_headers(dict(resp.getheaders()))
 | 
			
		||||
        return Image(self, meta)
 | 
			
		||||
 | 
			
		||||
    def data(self, image):
 | 
			
		||||
@@ -84,7 +84,7 @@ class ImageManager(base.Manager):
 | 
			
		||||
        :rtype: iterable containing image data
 | 
			
		||||
        """
 | 
			
		||||
        image_id = base.getid(image)
 | 
			
		||||
        resp, body = self.api.raw_request('GET', '/v1/images/%s' % image_id)
 | 
			
		||||
        _, body = self.api.raw_request('GET', '/v1/images/%s' % image_id)
 | 
			
		||||
        return body
 | 
			
		||||
 | 
			
		||||
    def list(self, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,11 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import StringIO
 | 
			
		||||
 | 
			
		||||
from glanceclient.common import http
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeAPI(object):
 | 
			
		||||
    def __init__(self, fixtures):
 | 
			
		||||
@@ -25,7 +30,18 @@ class FakeAPI(object):
 | 
			
		||||
        return self.fixtures[url][method]
 | 
			
		||||
 | 
			
		||||
    def raw_request(self, *args, **kwargs):
 | 
			
		||||
        return self._request(*args, **kwargs)
 | 
			
		||||
        fixture = self._request(*args, **kwargs)
 | 
			
		||||
        body_iter = http.ResponseBodyIterator(StringIO.StringIO(fixture[1]))
 | 
			
		||||
        return FakeResponse(fixture[0]), body_iter
 | 
			
		||||
 | 
			
		||||
    def json_request(self, *args, **kwargs):
 | 
			
		||||
        return self._request(*args, **kwargs)
 | 
			
		||||
        fixture = self._request(*args, **kwargs)
 | 
			
		||||
        return FakeResponse(fixture[0]), fixture[1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeResponse(object):
 | 
			
		||||
    def __init__(self, headers):
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
 | 
			
		||||
    def getheaders(self):
 | 
			
		||||
        return copy.deepcopy(self.headers).items()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
argparse
 | 
			
		||||
httplib2
 | 
			
		||||
prettytable>=0.6,<0.7
 | 
			
		||||
python-keystoneclient>=0.1,<0.2
 | 
			
		||||
warlock==0.1.0
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user