Skip to content

Commit 24f6c62

Browse files
author
Pádraig Brady
committed
support preallocated VM images
Use fallocate where available to allocate file system blocks efficiently when the VM is initially provisioned. This will give immediate feedback if enough space isn't available. Also it may significantly improve performance on writes to new blocks, and may even improve I/O performance to prewritten blocks due to reduced fragmentation. Also noted is the potential to subsume the less general libvirt_sparse_logical_volumes option into the new more general preallocate_images option. Implements bp: preallocated-images Change-Id: I4bbb8616416c9dca3a3a8894ebf764a0b6b8dbc9
1 parent 4e54c59 commit 24f6c62

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

nova/tests/test_imagebackend.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from nova.openstack.common import uuidutils
2323
from nova import test
2424
from nova.tests import fake_libvirt_utils
25+
from nova.tests import fake_utils
2526
from nova.virt.libvirt import imagebackend
2627

2728
CONF = cfg.CONF
@@ -125,6 +126,27 @@ def test_cache_template_exists(self):
125126

126127
self.mox.VerifyAll()
127128

129+
def test_prealloc_image(self):
130+
CONF.set_override('preallocate_images', 'space')
131+
132+
fake_utils.fake_execute_clear_log()
133+
fake_utils.stub_out_utils_execute(self.stubs)
134+
image = self.image_class(self.INSTANCE, self.NAME)
135+
136+
def fake_fetch(target, *args, **kwargs):
137+
return
138+
139+
self.stubs.Set(os.path, 'exists', lambda _: True)
140+
141+
# Call twice to verify testing fallocate is only called once.
142+
image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
143+
image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
144+
145+
self.assertEqual(fake_utils.fake_execute_get_log(),
146+
['fallocate -n -l 1 %s.fallocate_test' % self.PATH,
147+
'fallocate -n -l %s %s' % (self.SIZE, self.PATH),
148+
'fallocate -n -l %s %s' % (self.SIZE, self.PATH)])
149+
128150

129151
class RawTestCase(_ImageTestCase, test.TestCase):
130152

@@ -361,6 +383,22 @@ def test_create_image_generated_negative(self):
361383
ephemeral_size=None)
362384
self.mox.VerifyAll()
363385

386+
def test_prealloc_image(self):
387+
CONF.set_override('preallocate_images', 'space')
388+
389+
fake_utils.fake_execute_clear_log()
390+
fake_utils.stub_out_utils_execute(self.stubs)
391+
image = self.image_class(self.INSTANCE, self.NAME)
392+
393+
def fake_fetch(target, *args, **kwargs):
394+
return
395+
396+
self.stubs.Set(os.path, 'exists', lambda _: True)
397+
398+
image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
399+
400+
self.assertEqual(fake_utils.fake_execute_get_log(), [])
401+
364402

365403
class BackendTestCase(test.TestCase):
366404
INSTANCE = {'name': 'fake-instance',

nova/virt/driver.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
default=None,
4040
help='The default format an ephemeral_volume will be '
4141
'formatted with on creation.'),
42+
cfg.StrOpt('preallocate_images',
43+
default='none',
44+
help='VM image preallocation mode: '
45+
'"none" => no storage provisioning is done up front, '
46+
'"space" => storage is fully allocated at instance start'),
4247
cfg.BoolOpt('use_cow_images',
4348
default=True,
4449
help='Whether to use cow images'),

nova/virt/libvirt/imagebackend.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from nova.openstack.common import excutils
2424
from nova.openstack.common import fileutils
2525
from nova.openstack.common import lockutils
26+
from nova.openstack.common import log as logging
2627
from nova import utils
2728
from nova.virt.disk import api as disk
2829
from nova.virt import images
@@ -52,6 +53,9 @@
5253
CONF = cfg.CONF
5354
CONF.register_opts(__imagebackend_opts)
5455
CONF.import_opt('base_dir_name', 'nova.virt.libvirt.imagecache')
56+
CONF.import_opt('preallocate_images', 'nova.virt.driver')
57+
58+
LOG = logging.getLogger(__name__)
5559

5660

5761
class Image(object):
@@ -67,6 +71,7 @@ def __init__(self, source_type, driver_format, is_block_dev=False):
6771
self.source_type = source_type
6872
self.driver_format = driver_format
6973
self.is_block_dev = is_block_dev
74+
self.preallocate = False
7075

7176
# NOTE(mikal): We need a lock directory which is shared along with
7277
# instance files, to cover the scenario where multiple compute nodes
@@ -133,6 +138,25 @@ def call_if_not_exists(target, *args, **kwargs):
133138
self.create_image(call_if_not_exists, base, size,
134139
*args, **kwargs)
135140

141+
if size and self.preallocate and self._can_fallocate():
142+
utils.execute('fallocate', '-n', '-l', size, self.path)
143+
144+
def _can_fallocate(self):
145+
"""Check once per class, whether fallocate(1) is available,
146+
and that the instances directory supports fallocate(2).
147+
"""
148+
can_fallocate = getattr(self.__class__, 'can_fallocate', None)
149+
if can_fallocate is None:
150+
_out, err = utils.trycmd('fallocate', '-n', '-l', '1',
151+
self.path + '.fallocate_test')
152+
utils.delete_if_exists(self.path + '.fallocate_test')
153+
can_fallocate = not err
154+
self.__class__.can_fallocate = can_fallocate
155+
if not can_fallocate:
156+
LOG.error('Unable to preallocate_images=%s at path: %s' %
157+
(CONF.preallocate_images, self.path))
158+
return can_fallocate
159+
136160
def snapshot_create(self):
137161
raise NotImplementedError
138162

@@ -152,6 +176,7 @@ def __init__(self, instance=None, disk_name=None, path=None,
152176
os.path.join(libvirt_utils.get_instance_path(instance),
153177
disk_name))
154178
self.snapshot_name = snapshot_name
179+
self.preallocate = CONF.preallocate_images != 'none'
155180

156181
def create_image(self, prepare_template, base, size, *args, **kwargs):
157182
@lockutils.synchronized(base, 'nova-', external=True,
@@ -190,11 +215,15 @@ def __init__(self, instance=None, disk_name=None, path=None,
190215
os.path.join(libvirt_utils.get_instance_path(instance),
191216
disk_name))
192217
self.snapshot_name = snapshot_name
218+
self.preallocate = CONF.preallocate_images != 'none'
193219

194220
def create_image(self, prepare_template, base, size, *args, **kwargs):
195221
@lockutils.synchronized(base, 'nova-', external=True,
196222
lock_path=self.lock_path)
197223
def copy_qcow2_image(base, target, size):
224+
# TODO(pbrady): Consider copying the cow image here
225+
# with preallocation=metadata set for performance reasons.
226+
# This would be keyed on a 'preallocate_images' setting.
198227
libvirt_utils.create_cow_image(base, target)
199228
if size:
200229
disk.extend(target, size)
@@ -241,13 +270,19 @@ def __init__(self, instance=None, disk_name=None, path=None,
241270
self.escape(disk_name))
242271
self.path = os.path.join('/dev', self.vg, self.lv)
243272

273+
# TODO(pbrady): possibly deprecate libvirt_sparse_logical_volumes
274+
# for the more general preallocate_images
244275
self.sparse = CONF.libvirt_sparse_logical_volumes
276+
self.preallocate = not self.sparse
245277

246278
if snapshot_name:
247279
self.snapshot_name = snapshot_name
248280
self.snapshot_path = os.path.join('/dev', self.vg,
249281
self.snapshot_name)
250282

283+
def _can_fallocate(self):
284+
return False
285+
251286
def create_image(self, prepare_template, base, size, *args, **kwargs):
252287
@lockutils.synchronized(base, 'nova-', external=True,
253288
lock_path=self.lock_path)

0 commit comments

Comments
 (0)