Convert limiting_iter to LimitingReader.

Glance commit 3c69df5aa7 added a new
limiting_iter function in utils which we use to limit reads to
image data being uploaded. This simple generator doesn't work
with image backends like the Swift store which require a 'read'
method.

This patch swaps out the simple limiting_iter function for a
LimitingReader class which supports both 'read' and __iter__
functions.

Fixes LP Bug #1039212 which cause the exception below when used
with the previous code:

AttributeError: 'CooperativeReader' object has no attribute 'read'

Change-Id: I87d9a30f7afe0207386d621050312374ced161d5
This commit is contained in:
Dan Prince 2012-08-20 21:29:44 -04:00
parent 3c69df5aa7
commit a6de4de65b
3 changed files with 65 additions and 21 deletions

View File

@ -834,7 +834,7 @@ class ImageDeserializer(wsgi.JSONRequestDeserializer):
data = request.body_file if self.has_body(request) else None
if image_size is None and data is not None:
data = utils.limiting_iter(data, CONF.image_size_cap)
data = utils.LimitingReader(data, CONF.image_size_cap)
#NOTE(bcwaldon): this is a hack to make sure the downstream code
# gets the correct image data

View File

@ -125,18 +125,34 @@ class CooperativeReader(object):
return cooperative_iter(self.fd.__iter__())
def limiting_iter(iter, limit):
class LimitingReader(object):
"""
Iterator designed to fail when reading image data past the configured
Reader designed to fail when reading image data past the configured
allowable amount.
"""
bytes_read = 0
for chunk in iter:
bytes_read += len(chunk)
if bytes_read > limit:
def __init__(self, data, limit):
"""
:param data: Underlying image data object
:param limit: maximum number of bytes the reader should allow
"""
self.data = data
self.limit = limit
self.bytes_read = 0
def __iter__(self):
for chunk in self.data:
self.bytes_read += len(chunk)
if self.bytes_read > self.limit:
raise exception.ImageSizeLimitExceeded()
else:
yield chunk
def read(self, i):
result = self.data.read(i)
self.bytes_read += len(result)
if self.bytes_read > self.limit:
raise exception.ImageSizeLimitExceeded()
else:
yield chunk
return result
def image_meta_to_http_headers(image_meta):

View File

@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import StringIO
import tempfile
from glance.common import exception
@ -72,18 +73,45 @@ class TestUtils(test_utils.BaseTestCase):
self.assertEquals(bytes_read, BYTES)
def test_limited_iter_reads_succeed(self):
fap = 'abcdefghij'
fap = utils.limiting_iter(fap, 10)
def test_limiting_reader(self):
"""Ensure limiting reader class accesses all bytes of file"""
BYTES = 1024
bytes_read = 0
for chunk in fap:
data = StringIO.StringIO("*" * BYTES)
for chunk in utils.LimitingReader(data, BYTES):
bytes_read += len(chunk)
self.assertEqual(10, bytes_read)
def test_limited_iter_reads_fail(self):
fap = 'abcdefghij'
fap = utils.limiting_iter(fap, 9)
for i, chunk in enumerate(fap):
if i == 8:
break
self.assertRaises(exception.ImageSizeLimitExceeded, fap.next)
self.assertEquals(bytes_read, BYTES)
bytes_read = 0
data = StringIO.StringIO("*" * BYTES)
reader = utils.LimitingReader(data, BYTES)
byte = reader.read(1)
while len(byte) != 0:
bytes_read += 1
byte = reader.read(1)
self.assertEquals(bytes_read, BYTES)
def test_limiting_reader_fails(self):
"""Ensure limiting reader class throws exceptions if limit exceeded"""
BYTES = 1024
def _consume_all_iter():
bytes_read = 0
data = StringIO.StringIO("*" * BYTES)
for chunk in utils.LimitingReader(data, BYTES - 1):
bytes_read += len(chunk)
self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_iter)
def _consume_all_read():
bytes_read = 0
data = StringIO.StringIO("*" * BYTES)
reader = utils.LimitingReader(data, BYTES - 1)
byte = reader.read(1)
while len(byte) != 0:
bytes_read += 1
byte = reader.read(1)
self.assertRaises(exception.ImageSizeLimitExceeded, _consume_all_read)