diff options
-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 994f85ec1..13b8bead8 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 1a4d7f603..92c42ce3e 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 @@ -145,6 +150,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 @@ -164,6 +188,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, @@ -202,11 +227,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) @@ -253,13 +282,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) |