summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPádraig Brady <pbrady@redhat.com>2013-02-15 18:33:19 +0000
committerPádraig Brady <pbrady@redhat.com>2013-02-20 03:45:53 +0000
commit24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07 (patch)
tree70269a34635d936d92ff115911181cefafc4bbc5
parent4e54c59b8888a2c5a8ab41c213d3bff2faba5570 (diff)
downloadnova-24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07.tar.gz
nova-24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07.tar.xz
nova-24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07.zip
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
-rw-r--r--nova/tests/test_imagebackend.py38
-rwxr-xr-xnova/virt/driver.py5
-rwxr-xr-xnova/virt/libvirt/imagebackend.py35
3 files changed, 78 insertions, 0 deletions
diff --git a/nova/tests/test_imagebackend.py b/nova/tests/test_imagebackend.py
index bd88fcd60..15511cabb 100644
--- a/nova/tests/test_imagebackend.py
+++ b/nova/tests/test_imagebackend.py
@@ -22,6 +22,7 @@ from nova.openstack.common import cfg
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 @@ class _ImageTestCase(object):
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 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
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',
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index ba0dfbafe..7744ff4c1 100755
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -39,6 +39,11 @@ driver_opts = [
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'),
diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
index ba75ccf8b..6c8ee0078 100755
--- a/nova/virt/libvirt/imagebackend.py
+++ b/nova/virt/libvirt/imagebackend.py
@@ -23,6 +23,7 @@ from nova.openstack.common import cfg
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 @@ __imagebackend_opts = [
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 @@ class Image(object):
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 @@ class Image(object):
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 @@ class Raw(Image):
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 @@ class Qcow2(Image):
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 @@ class Lvm(Image):
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)