Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit 24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07 1 parent 4e54c59
@pixelb pixelb authored
View
38 nova/tests/test_imagebackend.py
@@ -22,6 +22,7 @@
from nova.openstack.common import uuidutils
from nova import test
from nova.tests import fake_libvirt_utils
+from nova.tests import fake_utils
from nova.virt.libvirt import imagebackend
CONF = cfg.CONF
@@ -125,6 +126,27 @@ def test_cache_template_exists(self):
self.mox.VerifyAll()
+ def test_prealloc_image(self):
+ CONF.set_override('preallocate_images', 'space')
+
+ fake_utils.fake_execute_clear_log()
+ fake_utils.stub_out_utils_execute(self.stubs)
+ image = self.image_class(self.INSTANCE, self.NAME)
+
+ def fake_fetch(target, *args, **kwargs):
+ return
+
+ self.stubs.Set(os.path, 'exists', lambda _: True)
+
+ # Call twice to verify testing fallocate is only called once.
+ image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
+ image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
+
+ self.assertEqual(fake_utils.fake_execute_get_log(),
+ ['fallocate -n -l 1 %s.fallocate_test' % self.PATH,
+ 'fallocate -n -l %s %s' % (self.SIZE, self.PATH),
+ 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)])
+
class RawTestCase(_ImageTestCase, test.TestCase):
@@ -361,6 +383,22 @@ def test_create_image_generated_negative(self):
ephemeral_size=None)
self.mox.VerifyAll()
+ def test_prealloc_image(self):
+ CONF.set_override('preallocate_images', 'space')
+
+ fake_utils.fake_execute_clear_log()
+ fake_utils.stub_out_utils_execute(self.stubs)
+ image = self.image_class(self.INSTANCE, self.NAME)
+
+ def fake_fetch(target, *args, **kwargs):
+ return
+
+ self.stubs.Set(os.path, 'exists', lambda _: True)
+
+ image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
+
+ self.assertEqual(fake_utils.fake_execute_get_log(), [])
+
class BackendTestCase(test.TestCase):
INSTANCE = {'name': 'fake-instance',
View
5 nova/virt/driver.py
@@ -39,6 +39,11 @@
default=None,
help='The default format an ephemeral_volume will be '
'formatted with on creation.'),
+ cfg.StrOpt('preallocate_images',
+ default='none',
+ help='VM image preallocation mode: '
+ '"none" => no storage provisioning is done up front, '
+ '"space" => storage is fully allocated at instance start'),
cfg.BoolOpt('use_cow_images',
default=True,
help='Whether to use cow images'),
View
35 nova/virt/libvirt/imagebackend.py
@@ -23,6 +23,7 @@
from nova.openstack.common import excutils
from nova.openstack.common import fileutils
from nova.openstack.common import lockutils
+from nova.openstack.common import log as logging
from nova import utils
from nova.virt.disk import api as disk
from nova.virt import images
@@ -52,6 +53,9 @@
CONF = cfg.CONF
CONF.register_opts(__imagebackend_opts)
CONF.import_opt('base_dir_name', 'nova.virt.libvirt.imagecache')
+CONF.import_opt('preallocate_images', 'nova.virt.driver')
+
+LOG = logging.getLogger(__name__)
class Image(object):
@@ -67,6 +71,7 @@ def __init__(self, source_type, driver_format, is_block_dev=False):
self.source_type = source_type
self.driver_format = driver_format
self.is_block_dev = is_block_dev
+ self.preallocate = False
# NOTE(mikal): We need a lock directory which is shared along with
# instance files, to cover the scenario where multiple compute nodes
@@ -133,6 +138,25 @@ def call_if_not_exists(target, *args, **kwargs):
self.create_image(call_if_not_exists, base, size,
*args, **kwargs)
+ if size and self.preallocate and self._can_fallocate():
+ utils.execute('fallocate', '-n', '-l', size, self.path)
+
+ def _can_fallocate(self):
+ """Check once per class, whether fallocate(1) is available,
+ and that the instances directory supports fallocate(2).
+ """
+ can_fallocate = getattr(self.__class__, 'can_fallocate', None)
+ if can_fallocate is None:
+ _out, err = utils.trycmd('fallocate', '-n', '-l', '1',
+ self.path + '.fallocate_test')
+ utils.delete_if_exists(self.path + '.fallocate_test')
+ can_fallocate = not err
+ self.__class__.can_fallocate = can_fallocate
+ if not can_fallocate:
+ LOG.error('Unable to preallocate_images=%s at path: %s' %
+ (CONF.preallocate_images, self.path))
+ return can_fallocate
+
def snapshot_create(self):
raise NotImplementedError
@@ -152,6 +176,7 @@ def __init__(self, instance=None, disk_name=None, path=None,
os.path.join(libvirt_utils.get_instance_path(instance),
disk_name))
self.snapshot_name = snapshot_name
+ self.preallocate = CONF.preallocate_images != 'none'
def create_image(self, prepare_template, base, size, *args, **kwargs):
@lockutils.synchronized(base, 'nova-', external=True,
@@ -190,11 +215,15 @@ def __init__(self, instance=None, disk_name=None, path=None,
os.path.join(libvirt_utils.get_instance_path(instance),
disk_name))
self.snapshot_name = snapshot_name
+ self.preallocate = CONF.preallocate_images != 'none'
def create_image(self, prepare_template, base, size, *args, **kwargs):
@lockutils.synchronized(base, 'nova-', external=True,
lock_path=self.lock_path)
def copy_qcow2_image(base, target, size):
+ # TODO(pbrady): Consider copying the cow image here
+ # with preallocation=metadata set for performance reasons.
+ # This would be keyed on a 'preallocate_images' setting.
libvirt_utils.create_cow_image(base, target)
if size:
disk.extend(target, size)
@@ -241,13 +270,19 @@ def __init__(self, instance=None, disk_name=None, path=None,
self.escape(disk_name))
self.path = os.path.join('/dev', self.vg, self.lv)
+ # TODO(pbrady): possibly deprecate libvirt_sparse_logical_volumes
+ # for the more general preallocate_images
self.sparse = CONF.libvirt_sparse_logical_volumes
+ self.preallocate = not self.sparse
if snapshot_name:
self.snapshot_name = snapshot_name
self.snapshot_path = os.path.join('/dev', self.vg,
self.snapshot_name)
+ def _can_fallocate(self):
+ return False
+
def create_image(self, prepare_template, base, size, *args, **kwargs):
@lockutils.synchronized(base, 'nova-', external=True,
lock_path=self.lock_path)
Please sign in to comment.
Something went wrong with that request. Please try again.