diff options
author | Pádraig Brady <pbrady@redhat.com> | 2013-02-15 18:33:19 +0000 |
---|---|---|
committer | Pádraig Brady <pbrady@redhat.com> | 2013-02-20 03:45:53 +0000 |
commit | 24f6c62ad776dc0fc85ce29eb34e7e0a1f270d07 (patch) | |
tree | 70269a34635d936d92ff115911181cefafc4bbc5 | |
parent | 4e54c59b8888a2c5a8ab41c213d3bff2faba5570 (diff) | |
download | nova-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.py | 38 | ||||
-rwxr-xr-x | nova/virt/driver.py | 5 | ||||
-rwxr-xr-x | nova/virt/libvirt/imagebackend.py | 35 |
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) |