Skip to content

Commit 5148c96

Browse files
committed
Decouple read chunk size from write chunk size
When using the copy_from option, readers and writers can have different speeds to respectively read and write. A reader timeout will happen if the writer is slow and the writer is being asked to write a lot. This is currently happening when using the VMware store and copying from an HTTP server. The reader is reading 16MB which takes too long to upload to vCenter which is causing a timeout from the HTTP server. The writer should be able to control the size of the chunks being read when using copy_from: this way the writer will write fast enough to not make the reader timeout. This patch addresses the issue by introducing the notion of read chunk size and write chunk size. Each store can have its own value for read and write. The write chunk size of the destination store will be used as the read chunk size of the source store in case of an image-create where the copy_from option is specified. Closes-Bug: #1336168 Signed-off-by: Arnaud Legendre <[email protected]> Signed-off-by: Zhi Yan Liu <[email protected]> Change-Id: I4e0c563b8f3a5ced8f65fcca83d341a97729a5d4
1 parent 323aba6 commit 5148c96

File tree

14 files changed

+61
-48
lines changed

14 files changed

+61
-48
lines changed

glance/api/v1/images.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,10 @@ def _external_source(self, image_meta, req):
447447
return Controller._validate_source(source, req)
448448

449449
@staticmethod
450-
def _get_from_store(context, where):
450+
def _get_from_store(context, where, dest=None):
451451
try:
452-
image_data, image_size = get_from_backend(context, where)
452+
image_data, image_size = get_from_backend(
453+
context, where, dest=dest)
453454
except exception.NotFound as e:
454455
raise HTTPNotFound(explanation=e.msg)
455456
image_size = int(image_size) if image_size else None
@@ -571,11 +572,15 @@ def _upload(self, req, image_meta):
571572
:retval The location where the image was stored
572573
"""
573574

575+
scheme = req.headers.get('x-image-meta-store', CONF.default_store)
576+
store = self.get_store_or_400(req, scheme)
577+
574578
copy_from = self._copy_from(req)
575579
if copy_from:
576580
try:
577581
image_data, image_size = self._get_from_store(req.context,
578-
copy_from)
582+
copy_from,
583+
dest=store)
579584
except Exception as e:
580585
upload_utils.safe_kill(req, image_meta['id'])
581586
msg = ("Copy from external source failed: %s" %
@@ -594,10 +599,6 @@ def _upload(self, req, image_meta):
594599

595600
image_data = req.body_file
596601

597-
scheme = req.headers.get('x-image-meta-store', CONF.default_store)
598-
599-
store = self.get_store_or_400(req, scheme)
600-
601602
image_id = image_meta['id']
602603
LOG.debug("Setting image %s to status 'saving'", image_id)
603604
registry.update_image_metadata(req.context, image_id,
@@ -1102,7 +1103,7 @@ def _deserialize(self, request):
11021103
if image_size is None and data is not None:
11031104
data = utils.LimitingReader(data, CONF.image_size_cap)
11041105

1105-
#NOTE(bcwaldon): this is a hack to make sure the downstream code
1106+
# NOTE(bcwaldon): this is a hack to make sure the downstream code
11061107
# gets the correct image data
11071108
request.body_file = data
11081109

glance/store/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,12 @@ def get_from_backend(context, uri, **kwargs):
258258
"""Yields chunks of data from backend specified by uri"""
259259

260260
loc = location.get_location_from_uri(uri)
261-
store = get_store_from_uri(context, uri, loc)
262-
261+
src_store = get_store_from_uri(context, uri, loc)
262+
dest_store = kwargs.get('dest')
263+
if dest_store is not None:
264+
src_store.READ_CHUNKSIZE = dest_store.WRITE_CHUNKSIZE
263265
try:
264-
return store.get(loc)
266+
return src_store.get(loc)
265267
except NotImplementedError:
266268
raise exception.StoreGetNotSupported
267269

glance/store/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
class Store(object):
2929

30-
CHUNKSIZE = 16 * units.Mi # 16M
30+
READ_CHUNKSIZE = 16 * units.Mi # 16M
31+
WRITE_CHUNKSIZE = READ_CHUNKSIZE
3132

3233
@staticmethod
3334
def _unconfigured(*args, **kwargs):

glance/store/filesystem.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ class ChunkedFile(object):
8989
something that can iterate over a large file
9090
"""
9191

92-
CHUNKSIZE = 65536
93-
9492
def __init__(self, filepath):
9593
self.filepath = filepath
9694
self.fp = open(self.filepath, 'rb')
@@ -100,7 +98,7 @@ def __iter__(self):
10098
try:
10199
if self.fp:
102100
while True:
103-
chunk = self.fp.read(ChunkedFile.CHUNKSIZE)
101+
chunk = self.fp.read(Store.READ_CHUNKSIZE)
104102
if chunk:
105103
yield chunk
106104
else:
@@ -117,6 +115,9 @@ def close(self):
117115

118116
class Store(glance.store.base.Store):
119117

118+
READ_CHUNKSIZE = 64 * units.Ki
119+
WRITE_CHUNKSIZE = READ_CHUNKSIZE
120+
120121
def get_schemes(self):
121122
return ('file', 'filesystem')
122123

@@ -431,7 +432,7 @@ def add(self, image_id, image_file, image_size):
431432
try:
432433
with open(filepath, 'wb') as f:
433434
for buf in utils.chunkreadable(image_file,
434-
ChunkedFile.CHUNKSIZE):
435+
self.WRITE_CHUNKSIZE):
435436
bytes_written += len(buf)
436437
checksum.update(buf)
437438
f.write(buf)

glance/store/http.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def get(self, location):
122122
"""
123123
conn, resp, content_length = self._query(location, 'GET')
124124

125-
iterator = http_response_iterator(conn, resp, self.CHUNKSIZE)
125+
iterator = http_response_iterator(conn, resp, self.READ_CHUNKSIZE)
126126

127127
class ResponseIndexable(glance.store.Indexable):
128128
def another(self):

glance/store/rbd.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ def configure_add(self):
188188
itself, it should raise `exception.BadStoreConfiguration`
189189
"""
190190
try:
191-
self.chunk_size = CONF.rbd_store_chunk_size * units.Mi
191+
self.READ_CHUNKSIZE = CONF.rbd_store_chunk_size * units.Mi
192+
self.WRITE_CHUNKSIZE = self.READ_CHUNKSIZE
192193

193194
# these must not be unicode since they will be passed to a
194195
# non-unicode-aware C library
@@ -323,7 +324,7 @@ def add(self, image_id, image_file, image_size):
323324
if hasattr(conn, 'get_fsid'):
324325
fsid = conn.get_fsid()
325326
with conn.open_ioctx(self.pool) as ioctx:
326-
order = int(math.log(self.chunk_size, 2))
327+
order = int(math.log(self.WRITE_CHUNKSIZE, 2))
327328
LOG.debug('creating image %(name)s with order %(order)d and '
328329
'size %(size)d',
329330
{'name': text_type(image_name),
@@ -345,7 +346,7 @@ def add(self, image_id, image_file, image_size):
345346
bytes_written = 0
346347
offset = 0
347348
chunks = utils.chunkreadable(image_file,
348-
self.chunk_size)
349+
self.WRITE_CHUNKSIZE)
349350
for chunk in chunks:
350351
# If the image size provided is zero we need to do
351352
# a resize for the amount we are writing. This will

glance/store/s3.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,6 @@ class ChunkedFile(object):
257257
something that can iterate over a ``boto.s3.key.Key``
258258
"""
259259

260-
CHUNKSIZE = 65536
261-
262260
def __init__(self, fp):
263261
self.fp = fp
264262

@@ -267,7 +265,7 @@ def __iter__(self):
267265
try:
268266
if self.fp:
269267
while True:
270-
chunk = self.fp.read(ChunkedFile.CHUNKSIZE)
268+
chunk = self.fp.read(Store.READ_CHUNKSIZE)
271269
if chunk:
272270
yield chunk
273271
else:
@@ -295,6 +293,9 @@ def close(self):
295293
class Store(glance.store.base.Store):
296294
"""An implementation of the s3 adapter."""
297295

296+
READ_CHUNKSIZE = 64 * units.Ki
297+
WRITE_CHUNKSIZE = READ_CHUNKSIZE
298+
298299
EXAMPLE_URL = "s3://<ACCESS_KEY>:<SECRET_KEY>@<S3_URL>/<BUCKET>/<OBJ>"
299300

300301
def get_schemes(self):
@@ -372,11 +373,11 @@ def get(self, location):
372373
"""
373374
key = self._retrieve_key(location)
374375

375-
key.BufferSize = self.CHUNKSIZE
376+
key.BufferSize = self.READ_CHUNKSIZE
376377

377378
class ChunkedIndexable(glance.store.Indexable):
378379
def another(self):
379-
return (self.wrapped.fp.read(ChunkedFile.CHUNKSIZE)
380+
return (self.wrapped.fp.read(self.READ_CHUNKSIZE)
380381
if self.wrapped.fp else None)
381382

382383
return (ChunkedIndexable(ChunkedFile(key), key.size), key.size)
@@ -504,7 +505,7 @@ def _sanitize(uri):
504505
tmpdir = self.s3_store_object_buffer_dir
505506
temp_file = tempfile.NamedTemporaryFile(dir=tmpdir)
506507
checksum = hashlib.md5()
507-
for chunk in utils.chunkreadable(image_file, self.CHUNKSIZE):
508+
for chunk in utils.chunkreadable(image_file, self.WRITE_CHUNKSIZE):
508509
checksum.update(chunk)
509510
temp_file.write(chunk)
510511
temp_file.flush()

glance/store/sheepdog.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ def configure(self):
191191
"""
192192

193193
try:
194-
self.chunk_size = CONF.sheepdog_store_chunk_size * units.Mi
194+
self.READ_CHUNKSIZE = CONF.sheepdog_store_chunk_size * units.Mi
195+
self.WRITE_CHUNKSIZE = self.READ_CHUNKSIZE
195196
self.addr = CONF.sheepdog_store_address.strip()
196197
self.port = CONF.sheepdog_store_port
197198
except cfg.ConfigFileValueError as e:
@@ -231,7 +232,7 @@ def get(self, location):
231232

232233
loc = location.store_location
233234
image = SheepdogImage(self.addr, self.port, loc.image,
234-
self.chunk_size)
235+
self.READ_CHUNKSIZE)
235236
if not image.exist():
236237
raise exception.NotFound(_("Sheepdog image %s does not exist")
237238
% image.name)
@@ -250,7 +251,7 @@ def get_size(self, location):
250251

251252
loc = location.store_location
252253
image = SheepdogImage(self.addr, self.port, loc.image,
253-
self.chunk_size)
254+
self.READ_CHUNKSIZE)
254255
if not image.exist():
255256
raise exception.NotFound(_("Sheepdog image %s does not exist")
256257
% image.name)
@@ -272,7 +273,7 @@ def add(self, image_id, image_file, image_size):
272273
"""
273274

274275
image = SheepdogImage(self.addr, self.port, image_id,
275-
self.chunk_size)
276+
self.WRITE_CHUNKSIZE)
276277
if image.exist():
277278
raise exception.Duplicate(_("Sheepdog image %s already exists")
278279
% image_id)
@@ -285,7 +286,7 @@ def add(self, image_id, image_file, image_size):
285286
try:
286287
total = left = image_size
287288
while left > 0:
288-
length = min(self.chunk_size, left)
289+
length = min(self.WRITE_CHUNKSIZE, left)
289290
data = image_file.read(length)
290291
image.write(data, total - left, length)
291292
left -= length
@@ -311,7 +312,7 @@ def delete(self, location):
311312

312313
loc = location.store_location
313314
image = SheepdogImage(self.addr, self.port, loc.image,
314-
self.chunk_size)
315+
self.WRITe_CHUNKSIZE)
315316
if not image.exist():
316317
raise exception.NotFound(_("Sheepdog image %s does not exist") %
317318
loc.image)

glance/store/swift.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from glance import i18n
3232
from glance.openstack.common import excutils
3333
import glance.openstack.common.log as logging
34+
from glance.openstack.common import units
3435
import glance.store
3536
import glance.store.base
3637
import glance.store.location
@@ -350,7 +351,7 @@ def Store(context=None, loc=None, configure=True):
350351

351352

352353
class BaseStore(glance.store.base.Store):
353-
CHUNKSIZE = 65536
354+
READ_CHUNKSIZE = 64 * units.Ki
354355

355356
def get_schemes(self):
356357
return ('swift+https', 'swift', 'swift+http', 'swift+config')
@@ -379,7 +380,7 @@ def _get_object(self, location, connection=None, start=None):
379380
try:
380381
resp_headers, resp_body = connection.get_object(
381382
container=location.container, obj=location.obj,
382-
resp_chunk_size=self.CHUNKSIZE, headers=headers)
383+
resp_chunk_size=self.READ_CHUNKSIZE, headers=headers)
383384
except swiftclient.ClientException as e:
384385
if e.http_status == httplib.NOT_FOUND:
385386
msg = _("Swift could not find object %s.") % location.obj

glance/store/vmware_datastore.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from glance.openstack.common import excutils
3131
from glance.openstack.common import gettextutils
3232
import glance.openstack.common.log as logging
33+
from glance.openstack.common import units
3334
import glance.store
3435
import glance.store.base
3536
import glance.store.location
@@ -221,6 +222,8 @@ def parse_uri(self, uri):
221222
class Store(glance.store.base.Store):
222223
"""An implementation of the VMware datastore adapter."""
223224

225+
WRITE_CHUNKSIZE = units.Mi
226+
224227
def get_schemes(self):
225228
return (STORE_SCHEME,)
226229

@@ -366,7 +369,7 @@ def get(self, location):
366369
from glance.store.location.get_location_from_uri()
367370
"""
368371
conn, resp, content_length = self._query(location, 'GET')
369-
iterator = http_response_iterator(conn, resp, self.CHUNKSIZE)
372+
iterator = http_response_iterator(conn, resp, self.READ_CHUNKSIZE)
370373

371374
class ResponseIndexable(glance.store.Indexable):
372375

0 commit comments

Comments
 (0)