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
=== modified file 'glance/api/v1/images.py'
--- glance/api/v1/images.py 2011-08-02 17:43:16 +0000
+++ glance/api/v1/images.py 2011-08-03 19:09:46 +0000
@@ -335,7 +335,6 @@
335 try:335 try:
336 logger.debug("Uploading image data for image %(image_id)s "336 logger.debug("Uploading image data for image %(image_id)s "
337 "to %(store_name)s store", locals())337 "to %(store_name)s store", locals())
338 req.make_body_seekable()
339 location, size, checksum = store.add(image_meta['id'],338 location, size, checksum = store.add(image_meta['id'],
340 req.body_file)339 req.body_file)
341340
342341
=== modified file 'glance/store/s3.py'
--- glance/store/s3.py 2011-08-01 22:10:31 +0000
+++ glance/store/s3.py 2011-08-03 19:09:46 +0000
@@ -18,7 +18,9 @@
18"""Storage backend for S3 or Storage Servers that follow the S3 Protocol"""18"""Storage backend for S3 or Storage Servers that follow the S3 Protocol"""
1919
20import logging20import logging
21import hashlib
21import httplib22import httplib
23import tempfile
22import urlparse24import urlparse
2325
24from glance.common import config26from glance.common import config
@@ -305,12 +307,41 @@
305307
306 key = bucket_obj.new_key(obj_name)308 key = bucket_obj.new_key(obj_name)
307309
310 # We need to wrap image_file, which is a reference to the
311 # webob.Request.body_file, with a seekable file-like object,
312 # otherwise the call to set_contents_from_file() will die
313 # with an error about Input object has no method 'seek'. We
314 # might want to call webob.Request.make_body_seekable(), but
315 # unfortunately, that method copies the entire image into
316 # memory and results in LP Bug #818292 occurring. So, here
317 # we write temporary file in as memory-efficient manner as
318 # possible and then supply the temporary file to S3. We also
319 # take this opportunity to calculate the image checksum while
320 # writing the tempfile, so we don't need to call key.compute_md5()
321
322 logger.debug("Writing request body file to temporary "
323 "file for %s", loc.get_uri())
324 temp_file = tempfile.NamedTemporaryFile()
325
326 checksum = hashlib.md5()
327 chunk = image_file.read(self.CHUNKSIZE)
328 while chunk:
329 checksum.update(chunk)
330 temp_file.write(chunk)
331 chunk = image_file.read(self.CHUNKSIZE)
332 temp_file.flush()
333
334 logger.debug("Uploading temporary file to S3 for %s", loc.get_uri())
335
308 # OK, now upload the data into the key336 # OK, now upload the data into the key
309 obj_md5, _base64_digest = key.compute_md5(image_file)337 key.set_contents_from_file(open(temp_file.name, 'r+b'), replace=False)
310 key.set_contents_from_file(image_file, replace=False)
311 size = key.size338 size = key.size
312339 checksum_hex = checksum.hexdigest()
313 return (loc.get_uri(), size, obj_md5)340
341 logger.debug("Wrote %(size)d bytes to S3 key named %(obj_name)s with "
342 "checksum %(checksum_hex)s" % locals())
343
344 return (loc.get_uri(), size, checksum_hex)
314345
315 def delete(self, location):346 def delete(self, location):
316 """347 """
317348
=== modified file 'glance/tests/functional/test_s3.py'
--- glance/tests/functional/test_s3.py 2011-08-02 22:10:52 +0000
+++ glance/tests/functional/test_s3.py 2011-08-03 19:09:46 +0000
@@ -50,7 +50,8 @@
50 # Test machines can set the GLANCE_TEST_MIGRATIONS_CONF variable50 # Test machines can set the GLANCE_TEST_MIGRATIONS_CONF variable
51 # to override the location of the config file for migration testing51 # to override the location of the config file for migration testing
52 CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF',52 CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF',
53 os.path.join('tests', 'functional',53 os.path.join('glance', 'tests',
54 'functional',
54 'test_s3.conf'))55 'test_s3.conf'))
5556
56 def setUp(self):57 def setUp(self):
5758
=== modified file 'glance/tests/unit/test_s3_store.py'
--- glance/tests/unit/test_s3_store.py 2011-08-02 15:43:05 +0000
+++ glance/tests/unit/test_s3_store.py 2011-08-03 19:09:46 +0000
@@ -77,7 +77,8 @@
7777
78 def set_contents_from_file(self, fp, replace=False, **kwargs):78 def set_contents_from_file(self, fp, replace=False, **kwargs):
79 self.data = StringIO.StringIO()79 self.data = StringIO.StringIO()
80 self.data.write(fp.getvalue())80 for bytes in fp:
81 self.data.write(bytes)
81 self.size = self.data.len82 self.size = self.data.len
82 # Reset the buffer to start83 # Reset the buffer to start
83 self.data.seek(0)84 self.data.seek(0)

Subscribers

People subscribed via source and target branches