Merge lp:~jaypipes/glance/bug700162 into lp:~hudson-openstack/glance/trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Devin Carlen
Approved revision: 46
Merged at revision: 44
Proposed branch: lp:~jaypipes/glance/bug700162
Merge into: lp:~hudson-openstack/glance/trunk
Prerequisite: lp:~jaypipes/glance/bug705583
Diff against target: 134 lines (+54/-18)
3 files modified
glance/client.py (+35/-17)
glance/server.py (+3/-1)
tests/stubs.py (+16/-0)
To merge this branch: bzr merge lp:~jaypipes/glance/bug700162
Reviewer Review Type Date Requested Status
Devin Carlen (community) Approve
Rick Harris (community) Approve
Soren Hansen Pending
Ewan Mellor Pending
Review via email: mp+46969@code.launchpad.net

Commit message

Fixes LP Bug #700162: Images greater than 2GB cannot be uploaded
using glance.client.Client.

Description of the change

Fixes LP Bug #700162: Images greater than 2GB cannot be uploaded
using glance.client.Client.

This patch introduces chunked-transfer encoding to the client
classes. When the glance.client.BaseClient.do_request() method
is called and the method is either POST or PUT and the body
argument is a file-like object, then the do_request() method
builds a chunked-transfer encoded request and send()s chunks of
the body file-like object over the httplib connection.

Not sure how we fully test this, since we'd have to pack up
a very large file for use in testing, but I'm open to suggestions :)

To post a comment you must log in.
Revision history for this message
Rick Harris (rconradharris) wrote :

Perfect timing, hit this bug just now and there is already a patch :)

Overall, your fix looks really good. I had to make a couple of small tweaks to get it fully-working. My fixes are here:

https://code.launchpad.net/~rconradharris/glance/bug700162

> Not sure how we fully test this, since we'd have to pack up
> a very large file for use in testing, but I'm open to suggestion

I just tested the code functionally by uploading ubuntu-lucid into Glance. Without the fix I get a MemoryError, with it, it uploads successfully.

review: Needs Fixing
Revision history for this message
Ewan Mellor (ewanmellor) wrote :

For test, how about uploading a pattern through it? Just incrementing integers would do the trick. You can make a file-like object that generates the pattern, and stub out the actual send routine, and check that the pattern makes it through.

If the unit test rlimited itself (resource.setrlimit) then it wouldn't even need to stream too much -- just cap yourself at 100 MB and stream 200 MB through.

Revision history for this message
Ewan Mellor (ewanmellor) wrote :

lgtm.

Revision history for this message
Jay Pipes (jaypipes) wrote :

On Fri, Jan 21, 2011 at 12:00 AM, Rick Harris <email address hidden> wrote:
> Review: Needs Fixing
> Perfect timing, hit this bug just now and there is already a patch :)
>
> Overall, your fix looks really good.  I had to make a couple of small tweaks to get it fully-working. My fixes are here:
>
> https://code.launchpad.net/~rconradharris/glance/bug700162

Swett, thanks Rick! :) I'll merge your patch into mine and push shortly.

>> Not sure how we fully test this, since we'd have to pack up
>> a very large file for use in testing, but I'm open to suggestion
>
> I just tested the code functionally by uploading ubuntu-lucid into Glance. Without the fix I get a MemoryError, with it, it uploads successfully.

Great! I like Ewan's idea for testing below; I'll try to integrate
that idea in too.

-jay

Revision history for this message
Jay Pipes (jaypipes) wrote :

> Overall, your fix looks really good. I had to make a couple of small tweaks
> to get it fully-working. My fixes are here:
>
> https://code.launchpad.net/~rconradharris/glance/bug700162

Hmm, looks like you may not have run the test suite after making your changes :) I'll fix up the few errors that have popped up!

-jay

Revision history for this message
Rick Harris (rconradharris) wrote :

Shoot, yeah, had tunnel-vision trying to get nova-compute working on DomU. Thanks for catching that.

Revision history for this message
Jay Pipes (jaypipes) wrote :

OK all, please re-review. Tests now all pass and it seems that the bug is fixed...

Revision history for this message
Rick Harris (rconradharris) wrote :

lgtm, let's get this merged asap! :D

review: Approve
Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'glance/client.py'
2--- glance/client.py 2011-01-07 06:13:33 +0000
3+++ glance/client.py 2011-01-21 17:38:25 +0000
4@@ -70,6 +70,8 @@
5
6 """A base client class"""
7
8+ CHUNKSIZE = 65536
9+
10 def __init__(self, host, port, use_ssl):
11 """
12 Creates a new client to some service.
13@@ -103,12 +105,41 @@
14 :param action: part of URL after root netloc
15 :param body: string of data to send, or None (default)
16 :param headers: mapping of key/value pairs to add as headers
17+
18+ :note
19+
20+ If the body param has a read attribute, and method is either
21+ POST or PUT, this method will automatically conduct a chunked-transfer
22+ encoding and use the body as a file object, transferring chunks
23+ of data using the connection's send() method. This allows large
24+ objects to be transferred efficiently without buffering the entire
25+ body in memory.
26 """
27 try:
28 connection_type = self.get_connection_type()
29 headers = headers or {}
30 c = connection_type(self.host, self.port)
31- c.request(method, action, body, headers)
32+
33+ # Do a simple request or a chunked request, depending
34+ # on whether the body param is a file-like object and
35+ # the method is PUT or POST
36+ if hasattr(body, 'read') and method.lower() in ('post', 'put'):
37+ # Chunk it, baby...
38+ c.putrequest(method, action)
39+
40+ for header, value in headers.items():
41+ c.putheader(header, value)
42+ c.putheader('Transfer-Encoding', 'chunked')
43+ c.endheaders()
44+
45+ chunk = body.read(self.CHUNKSIZE)
46+ while chunk:
47+ c.send('%x\r\n%s\r\n' % (len(chunk), chunk))
48+ chunk = body.read(self.CHUNKSIZE)
49+ c.send('0\r\n\r\n')
50+ else:
51+ # Simple request...
52+ c.request(method, action, body, headers)
53 res = c.getresponse()
54 status_code = self.get_status_code(res)
55 if status_code == httplib.OK:
56@@ -185,8 +216,6 @@
57 :retval Tuple containing (image_meta, image_blob)
58 :raises exception.NotFound if image is not found
59 """
60- # TODO(jaypipes): Handle other registries than Registry...
61-
62 res = self.do_request("GET", "/images/%s" % image_id)
63
64 image = util.get_image_meta_from_headers(res)
65@@ -219,24 +248,13 @@
66 if image_meta is None:
67 image_meta = {}
68
69- if image_data:
70- if hasattr(image_data, 'read'):
71- # TODO(jaypipes): This is far from efficient. Implement
72- # chunked transfer encoding if size is not in image_meta
73- body = image_data.read()
74- else:
75- body = image_data
76- else:
77- body = None
78-
79- if not 'size' in image_meta.keys():
80- if body:
81- image_meta['size'] = len(body)
82-
83 headers = util.image_meta_to_http_headers(image_meta)
84
85 if image_data:
86+ body = image_data
87 headers['content-type'] = 'application/octet-stream'
88+ else:
89+ body = None
90
91 res = self.do_request("POST", "/images", body, headers)
92 data = json.loads(res.read())
93
94=== modified file 'glance/server.py'
95--- glance/server.py 2011-01-18 17:58:23 +0000
96+++ glance/server.py 2011-01-21 17:38:25 +0000
97@@ -324,7 +324,9 @@
98 """
99 image_meta = self._reserve(req)
100
101- if req.body:
102+ has_body = req.content_length or 'transfer-encoding' in req.headers
103+
104+ if has_body:
105 self._upload_and_activate(req, image_meta)
106 else:
107 if 'x-image-meta-location' in req.headers:
108
109=== modified file 'tests/stubs.py'
110--- tests/stubs.py 2011-01-18 17:58:23 +0000
111+++ tests/stubs.py 2011-01-21 17:38:25 +0000
112@@ -260,6 +260,22 @@
113 def close(self):
114 return True
115
116+ def putrequest(self, method, url):
117+ self.req = webob.Request.blank("/" + url.lstrip("/"))
118+ self.req.method = method
119+
120+ def putheader(self, key, value):
121+ self.req.headers[key] = value
122+
123+ def endheaders(self):
124+ pass
125+
126+ def send(self, data):
127+ # send() is called during chunked-transfer encoding, and
128+ # data is of the form %x\r\n%s\r\n. Strip off the %x and
129+ # only write the actual data in tests.
130+ self.req.body += data.split("\r\n")[1]
131+
132 def request(self, method, url, body=None, headers={}):
133 self.req = webob.Request.blank("/" + url.lstrip("/"))
134 self.req.method = method

Subscribers

People subscribed via source and target branches