From da360462a54501178753571cdfec800e899f38ae Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 11 Jul 2012 19:34:28 -0700 Subject: [PATCH] Wrap image data in iterator This is establishing the API for a future optimization. We want to be able to offer true socket-level caching, but can't do that with httplib2 right now. For now, we will just fake the optimization by returning an iterator over the image body, which happens to already be fully loaded into a string. Change-Id: I2d36e3cdd45b26d7c7c27ba050bf6a4b5765df6c --- glanceclient/common/http.py | 30 ++++++++++++++++++++++++++++-- glanceclient/v1/images.py | 6 ++++-- tests/v1/test_images.py | 4 ++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 0892a4df..4ada1914 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -5,6 +5,7 @@ OpenStack Client interface. Handles the REST calls and responses. import copy import logging import os +import StringIO import urlparse import httplib2 @@ -25,6 +26,7 @@ from glanceclient.common import exceptions logger = logging.getLogger(__name__) USER_AGENT = 'python-glanceclient' +CHUNKSIZE = 1024 * 64 # 64kB class HTTPClient(httplib2.Http): @@ -88,7 +90,12 @@ class HTTPClient(httplib2.Http): # Redirected. Reissue the request to the new location. return self._http_request(resp['location'], method, **kwargs) - return resp, body + #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)) + + return resp, body_iter def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) @@ -97,7 +104,8 @@ class HTTPClient(httplib2.Http): if 'body' in kwargs: kwargs['body'] = json.dumps(kwargs['body']) - resp, body = self._http_request(url, method, **kwargs) + resp, body_iter = self._http_request(url, method, **kwargs) + body = ''.join([chunk for chunk in body_iter]) if body: try: @@ -115,3 +123,21 @@ class HTTPClient(httplib2.Http): kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) + + +class ResponseBodyIterator(object): + """A class that acts as an iterator over an HTTP response.""" + + def __init__(self, resp): + self.resp = resp + + def __iter__(self): + while True: + yield self.next() + + def next(self): + chunk = self.resp.read(CHUNKSIZE) + if chunk: + return chunk + else: + raise StopIteration() diff --git a/glanceclient/v1/images.py b/glanceclient/v1/images.py index 5bee3597..4263bafd 100644 --- a/glanceclient/v1/images.py +++ b/glanceclient/v1/images.py @@ -159,8 +159,9 @@ class ImageManager(base.Manager): if copy_from is not None: hdrs['x-glance-api-copy-from'] = copy_from - resp, body = self.api.raw_request( + resp, body_iter = self.api.raw_request( 'POST', '/v1/images', headers=hdrs, body=image_data) + body = ''.join([c for c in body_iter]) return Image(self, json.loads(body)['image']) def update(self, image, **kwargs): @@ -199,6 +200,7 @@ class ImageManager(base.Manager): hdrs['x-glance-api-copy-from'] = copy_from url = '/v1/images/%s' % base.getid(image) - resp, body = self.api.raw_request( + resp, body_iter = self.api.raw_request( 'PUT', url, headers=hdrs, body=image_data) + body = ''.join([c for c in body_iter]) return Image(self, json.loads(body)['image']) diff --git a/tests/v1/test_images.py b/tests/v1/test_images.py index b3fe1281..dc2f2c17 100644 --- a/tests/v1/test_images.py +++ b/tests/v1/test_images.py @@ -131,7 +131,7 @@ class ImageManagerTest(unittest.TestCase): self.assertEqual(image.name, 'image-1') def test_data(self): - data = self.mgr.data('1') + data = ''.join([b for b in self.mgr.data('1')]) expect = [('GET', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'XXX') @@ -256,7 +256,7 @@ class ImageTest(unittest.TestCase): def test_data(self): image = self.mgr.get('1') - data = image.data() + data = ''.join([b for b in image.data()]) expect = [ ('HEAD', '/v1/images/1', {}, None), ('GET', '/v1/images/1', {}, None),