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

Proposed by Jay Pipes
Status: Merged
Approved by: Matt Dietz
Approved revision: 181
Merged at revision: 180
Proposed branch: lp:~jaypipes/glance/bug818292
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 99 lines (+39/-7)
4 files modified
glance/api/v1/images.py (+0/-1)
glance/store/s3.py (+35/-4)
glance/tests/functional/test_s3.py (+2/-1)
glance/tests/unit/test_s3_store.py (+2/-1)
To merge this branch: bzr merge lp:~jaypipes/glance/bug818292
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Matt Dietz (community) Approve
Josh Kearney (community) Approve
Review via email: mp+70351@code.launchpad.net

Description of the change

Removes the call to webob.Request.make_body_seekable() in the
general images controller to prevent the image from being copied
into memory. In the S3 controller, which needs a seekable file-like
object when calling boto.s3.Key.set_contents_from_file(), we work
around this by writing chunks of the request body to a tempfile on
the API node, then stream this tempfile to S3.

To post a comment you must log in.
Revision history for this message
Josh Kearney (jk0) wrote :

Verified in our lab. Thanks Jay!

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

I like it

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

Great job churning this patch out so quickly, Jay. Much appreciated.

In a follow-up patch we might want to consider deleting the temporary file once we're done with it.

Since we're using Python 2.6, we can add a delete=True to the NamedTemporaryFile initializer and that will automatically delete the file when we close it. Something like:

with NamedTemporaryFile(delete=True) as f:
  do a bunch of stuff

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

Good suggestion, Rick. I'll do that in a later commit.

Cheers,
jay

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'glance/api/v1/images.py'
2--- glance/api/v1/images.py 2011-08-02 17:43:16 +0000
3+++ glance/api/v1/images.py 2011-08-03 19:09:46 +0000
4@@ -335,7 +335,6 @@
5 try:
6 logger.debug("Uploading image data for image %(image_id)s "
7 "to %(store_name)s store", locals())
8- req.make_body_seekable()
9 location, size, checksum = store.add(image_meta['id'],
10 req.body_file)
11
12
13=== modified file 'glance/store/s3.py'
14--- glance/store/s3.py 2011-08-01 22:10:31 +0000
15+++ glance/store/s3.py 2011-08-03 19:09:46 +0000
16@@ -18,7 +18,9 @@
17 """Storage backend for S3 or Storage Servers that follow the S3 Protocol"""
18
19 import logging
20+import hashlib
21 import httplib
22+import tempfile
23 import urlparse
24
25 from glance.common import config
26@@ -305,12 +307,41 @@
27
28 key = bucket_obj.new_key(obj_name)
29
30+ # We need to wrap image_file, which is a reference to the
31+ # webob.Request.body_file, with a seekable file-like object,
32+ # otherwise the call to set_contents_from_file() will die
33+ # with an error about Input object has no method 'seek'. We
34+ # might want to call webob.Request.make_body_seekable(), but
35+ # unfortunately, that method copies the entire image into
36+ # memory and results in LP Bug #818292 occurring. So, here
37+ # we write temporary file in as memory-efficient manner as
38+ # possible and then supply the temporary file to S3. We also
39+ # take this opportunity to calculate the image checksum while
40+ # writing the tempfile, so we don't need to call key.compute_md5()
41+
42+ logger.debug("Writing request body file to temporary "
43+ "file for %s", loc.get_uri())
44+ temp_file = tempfile.NamedTemporaryFile()
45+
46+ checksum = hashlib.md5()
47+ chunk = image_file.read(self.CHUNKSIZE)
48+ while chunk:
49+ checksum.update(chunk)
50+ temp_file.write(chunk)
51+ chunk = image_file.read(self.CHUNKSIZE)
52+ temp_file.flush()
53+
54+ logger.debug("Uploading temporary file to S3 for %s", loc.get_uri())
55+
56 # OK, now upload the data into the key
57- obj_md5, _base64_digest = key.compute_md5(image_file)
58- key.set_contents_from_file(image_file, replace=False)
59+ key.set_contents_from_file(open(temp_file.name, 'r+b'), replace=False)
60 size = key.size
61-
62- return (loc.get_uri(), size, obj_md5)
63+ checksum_hex = checksum.hexdigest()
64+
65+ logger.debug("Wrote %(size)d bytes to S3 key named %(obj_name)s with "
66+ "checksum %(checksum_hex)s" % locals())
67+
68+ return (loc.get_uri(), size, checksum_hex)
69
70 def delete(self, location):
71 """
72
73=== modified file 'glance/tests/functional/test_s3.py'
74--- glance/tests/functional/test_s3.py 2011-08-02 22:10:52 +0000
75+++ glance/tests/functional/test_s3.py 2011-08-03 19:09:46 +0000
76@@ -50,7 +50,8 @@
77 # Test machines can set the GLANCE_TEST_MIGRATIONS_CONF variable
78 # to override the location of the config file for migration testing
79 CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF',
80- os.path.join('tests', 'functional',
81+ os.path.join('glance', 'tests',
82+ 'functional',
83 'test_s3.conf'))
84
85 def setUp(self):
86
87=== modified file 'glance/tests/unit/test_s3_store.py'
88--- glance/tests/unit/test_s3_store.py 2011-08-02 15:43:05 +0000
89+++ glance/tests/unit/test_s3_store.py 2011-08-03 19:09:46 +0000
90@@ -77,7 +77,8 @@
91
92 def set_contents_from_file(self, fp, replace=False, **kwargs):
93 self.data = StringIO.StringIO()
94- self.data.write(fp.getvalue())
95+ for bytes in fp:
96+ self.data.write(bytes)
97 self.size = self.data.len
98 # Reset the buffer to start
99 self.data.seek(0)

Subscribers

People subscribed via source and target branches