From b1d575f2ad1c2accc8e54a2ea631ef21ac86ad97 Mon Sep 17 00:00:00 2001 From: Mikhail Feoktistov Date: Tue, 13 Sep 2016 06:08:57 -0400 Subject: [PATCH] libvirt: ephemeral disk support for virtuozzo containers For virtuozzo containers we create ephemeral disk based on ploop format. After we create ploop disk, we should add 'read 'permission for all users. It's necessary because openstack user query info of this disk by qemu-img. Change-Id: I2d6dd043340322d4c4ac1efd38f993f08932a483 Implements: blueprint ephemeral-disk-ploop --- etc/nova/rootwrap.d/compute.filters | 3 ++ .../unit/virt/libvirt/fake_libvirt_utils.py | 4 ++ nova/tests/unit/virt/libvirt/test_driver.py | 14 +++++- .../unit/virt/libvirt/test_imagebackend.py | 7 +++ nova/tests/unit/virt/libvirt/test_utils.py | 28 +++++++++++ nova/virt/libvirt/driver.py | 40 ++++++++++----- nova/virt/libvirt/imagebackend.py | 49 +++++++++++++------ nova/virt/libvirt/utils.py | 28 +++++++++++ ...ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml | 3 ++ 9 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/bp-ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index 5a486a011fa5..e4718486b722 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -252,6 +252,9 @@ sync: CommandFilter, sync, root ploop: RegExpFilter, ploop, root, ploop, restore-descriptor, .* prl_disk_tool: RegExpFilter, prl_disk_tool, root, prl_disk_tool, resize, --size, .*M$, --resize_partition, --hdd, .* +# nova/virt/libvirt/utils.py: +ploop: RegExpFilter, ploop, root, ploop, init, -s, .*, -f, .*, -t, .*, .* + # nova/virt/libvirt/utils.py: 'xend', 'status' xend: CommandFilter, xend, root diff --git a/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py b/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py index bb30bdb1e723..5da52a3f4b68 100644 --- a/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py +++ b/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py @@ -34,6 +34,10 @@ def create_cow_image(backing_file, path): pass +def create_ploop_image(disk_format, path, size, fs_type): + pass + + def get_disk_size(path, format=None): return 0 diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 169bf438bb66..c3860422541f 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -10761,7 +10761,7 @@ class LibvirtConnTestCase(test.NoDBTestCase): backend.mock_create_ephemeral.assert_called_once_with( target=filename, ephemeral_size=100, fs_label='ephemeral0', is_block_dev=mock.sentinel.is_block_dev, os_type='linux', - specified_fs=None, context=self.context) + specified_fs=None, context=self.context, vm_mode=None) backend.disks['disk.eph0'].cache.assert_called_once_with( fetch_func=mock.ANY, context=self.context, filename=filename, size=100 * units.Gi, ephemeral_size=mock.ANY, @@ -10868,6 +10868,18 @@ class LibvirtConnTestCase(test.NoDBTestCase): drvr._create_ephemeral('/dev/something', 20, 'myVol', 'linux', is_block_dev=True) + @mock.patch.object(fake_libvirt_utils, 'create_ploop_image') + def test_create_ephemeral_parallels(self, mock_create_ploop): + self.flags(virt_type='parallels', group='libvirt') + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + drvr._create_ephemeral('/dev/something', 20, 'myVol', 'linux', + is_block_dev=False, + specified_fs='fs_format', + vm_mode=fields.VMMode.EXE) + mock_create_ploop.assert_called_once_with('expanded', + '/dev/something', + '20G', 'fs_format') + def test_create_swap_default(self): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) self.mox.StubOutWithMock(utils, 'execute') diff --git a/nova/tests/unit/virt/libvirt/test_imagebackend.py b/nova/tests/unit/virt/libvirt/test_imagebackend.py index 21271922ad8f..892ff0afa90e 100644 --- a/nova/tests/unit/virt/libvirt/test_imagebackend.py +++ b/nova/tests/unit/virt/libvirt/test_imagebackend.py @@ -1710,6 +1710,13 @@ class PloopTestCase(_ImageTestCase, test.NoDBTestCase): self.mox.VerifyAll() + def test_create_image_generated(self): + fn = mock.Mock() + image = self.image_class(self.INSTANCE, self.NAME) + image.create_image(fn, self.TEMPLATE_PATH, 2048, ephemeral_size=2) + fn.assert_called_with(target=self.PATH, + ephemeral_size=2) + def test_prealloc_image(self): self.flags(preallocate_images='space') fake_processutils.fake_execute_clear_log() diff --git a/nova/tests/unit/virt/libvirt/test_utils.py b/nova/tests/unit/virt/libvirt/test_utils.py index 7612d8b4dc60..9fb350e95be8 100644 --- a/nova/tests/unit/virt/libvirt/test_utils.py +++ b/nova/tests/unit/virt/libvirt/test_utils.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt import functools import os import tempfile @@ -39,6 +40,7 @@ from nova.virt.libvirt import utils as libvirt_utils CONF = cfg.CONF +@ddt.ddt class LibvirtUtilsTestCase(test.NoDBTestCase): @mock.patch('nova.utils.execute') @@ -334,6 +336,32 @@ ID TAG VM SIZE DATE VM CLOCK '/the/new/cow'),)] self.assertEqual(expected_args, mock_execute.call_args_list) + @ddt.unpack + @ddt.data({'fs_type': 'some_fs_type', + 'default_eph_format': None, + 'expected_fs_type': 'some_fs_type'}, + {'fs_type': None, + 'default_eph_format': None, + 'expected_fs_type': disk.FS_FORMAT_EXT4}, + {'fs_type': None, + 'default_eph_format': 'eph_format', + 'expected_fs_type': 'eph_format'}) + def test_create_ploop_image(self, fs_type, + default_eph_format, + expected_fs_type): + with mock.patch('nova.utils.execute') as mock_execute: + self.flags(default_ephemeral_format=default_eph_format) + libvirt_utils.create_ploop_image('expanded', '/some/path', + '5G', fs_type) + mock_execute.assert_has_calls([ + mock.call('mkdir', '-p', '/some/path'), + mock.call('ploop', 'init', '-s', '5G', + '-f', 'expanded', '-t', expected_fs_type, + '/some/path/root.hds', + run_as_root=True, check_exit_code=True), + mock.call('chmod', '-R', 'a+r', '/some/path', + run_as_root=True, check_exit_code=True)]) + def test_pick_disk_driver_name(self): type_map = {'kvm': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), 'qemu': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index ffcef901ebc0..7564090cff6b 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -2883,8 +2883,16 @@ class LibvirtDriver(driver.ComputeDriver): @staticmethod def _create_ephemeral(target, ephemeral_size, fs_label, os_type, is_block_dev=False, - context=None, specified_fs=None): + context=None, specified_fs=None, + vm_mode=None): if not is_block_dev: + if (CONF.libvirt.virt_type == "parallels" and + vm_mode == fields.VMMode.EXE): + + libvirt_utils.create_ploop_image('expanded', target, + '%dG' % ephemeral_size, + specified_fs) + return libvirt_utils.create_image('raw', target, '%dG' % ephemeral_size) # Run as root only for block devices. @@ -3059,13 +3067,15 @@ class LibvirtDriver(driver.ComputeDriver): file_extension = disk_api.get_file_extension_for_os_type( os_type_with_default) + vm_mode = fields.VMMode.get_from_instance(instance) ephemeral_gb = instance.flavor.ephemeral_gb if 'disk.local' in disk_mapping: disk_image = image('disk.local') fn = functools.partial(self._create_ephemeral, fs_label='ephemeral0', os_type=instance.os_type, - is_block_dev=disk_image.is_block_dev) + is_block_dev=disk_image.is_block_dev, + vm_mode=vm_mode) fname = "ephemeral_%s_%s" % (ephemeral_gb, file_extension) size = ephemeral_gb * units.Gi disk_image.cache(fetch_func=fn, @@ -3086,7 +3096,8 @@ class LibvirtDriver(driver.ComputeDriver): fn = functools.partial(self._create_ephemeral, fs_label='ephemeral%d' % idx, os_type=instance.os_type, - is_block_dev=disk_image.is_block_dev) + is_block_dev=disk_image.is_block_dev, + vm_mode=vm_mode) size = eph['size'] * units.Gi fname = "ephemeral_%s_%s" % (eph['size'], file_extension) disk_image.cache(fetch_func=fn, @@ -3512,6 +3523,19 @@ class LibvirtDriver(driver.ComputeDriver): block_device_mapping = driver.block_device_info_get_mapping( block_device_info) mount_rootfs = CONF.libvirt.virt_type == "lxc" + + def _get_ephemeral_devices(): + eph_devices = [] + for idx, eph in enumerate( + driver.block_device_info_get_ephemerals( + block_device_info)): + diskeph = self._get_guest_disk_config( + instance, + blockinfo.get_eph_disk(idx), + disk_mapping, inst_type) + eph_devices.append(diskeph) + return eph_devices + if mount_rootfs: fs = vconfig.LibvirtConfigGuestFilesys() fs.source_type = "mount" @@ -3531,6 +3555,7 @@ class LibvirtDriver(driver.ComputeDriver): if 'disk' in disk_mapping: fs = self._get_guest_fs_config(instance, "disk") devices.append(fs) + devices = devices + _get_ephemeral_devices() else: if rescue: @@ -3562,14 +3587,7 @@ class LibvirtDriver(driver.ComputeDriver): instance.default_ephemeral_device = ( block_device.prepend_dev(disklocal.target_dev)) - for idx, eph in enumerate( - driver.block_device_info_get_ephemerals( - block_device_info)): - diskeph = self._get_guest_disk_config( - instance, - blockinfo.get_eph_disk(idx), - disk_mapping, inst_type) - devices.append(diskeph) + devices = devices + _get_ephemeral_devices() if 'disk.swap' in disk_mapping: diskswap = self._get_guest_disk_config(instance, diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py index 06c739b0e0b9..889922980755 100644 --- a/nova/virt/libvirt/imagebackend.py +++ b/nova/virt/libvirt/imagebackend.py @@ -1026,11 +1026,18 @@ class Ploop(Image): self.resolve_driver_format() + # Create new ploop disk (in case of epehemeral) or + # copy ploop disk from glance image def create_image(self, prepare_template, base, size, *args, **kwargs): - filename = os.path.split(base)[-1] + filename = os.path.basename(base) + # Copy main file of ploop disk, restore DiskDescriptor.xml for it + # and resize if necessary @utils.synchronized(filename, external=True, lock_path=self.lock_path) - def create_ploop_image(base, target, size): + def _copy_ploop_image(base, target, size): + # Ploop disk is a directory with data file(root.hds) and + # DiskDescriptor.xml, so create this dir + fileutils.ensure_tree(target) image_path = os.path.join(target, "root.hds") libvirt_utils.copy_image(base, image_path) utils.execute('ploop', 'restore-descriptor', '-f', self.pcs_format, @@ -1038,7 +1045,28 @@ class Ploop(Image): if size: self.resize_image(size) - if not os.path.exists(self.path): + # Generating means that we create empty ploop disk + generating = 'image_id' not in kwargs + remove_func = functools.partial(fileutils.delete_if_exists, + remove=shutil.rmtree) + if generating: + if os.path.exists(self.path): + return + with fileutils.remove_path_on_error(self.path, remove=remove_func): + prepare_template(target=self.path, *args, **kwargs) + else: + # Create ploop disk from glance image + if not os.path.exists(base): + prepare_template(target=base, *args, **kwargs) + else: + # Disk already exists in cache, just update time + libvirt_utils.update_mtime(base) + self.verify_base_size(base, size) + + if os.path.exists(self.path): + return + + # Get format for ploop disk if CONF.force_raw_images: self.pcs_format = "raw" else: @@ -1058,19 +1086,8 @@ class Ploop(Image): image_id=kwargs["image_id"], reason=reason) - if not os.path.exists(base): - prepare_template(target=base, *args, **kwargs) - self.verify_base_size(base, size) - - if os.path.exists(self.path): - return - - fileutils.ensure_tree(self.path) - - remove_func = functools.partial(fileutils.delete_if_exists, - remove=shutil.rmtree) - with fileutils.remove_path_on_error(self.path, remove=remove_func): - create_ploop_image(base, self.path, size) + with fileutils.remove_path_on_error(self.path, remove=remove_func): + _copy_ploop_image(base, self.path, size) def resize_image(self, size): image = imgmodel.LocalFileImage(self.path, imgmodel.FORMAT_PLOOP) diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index 67b445400a92..0392e2120235 100644 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -32,6 +32,7 @@ from nova.i18n import _LI from nova.i18n import _LW from nova.objects import fields as obj_fields from nova import utils +from nova.virt.disk import api as disk from nova.virt import images from nova.virt.libvirt import config as vconfig from nova.virt.libvirt.volume import remotefs @@ -98,6 +99,33 @@ def create_cow_image(backing_file, path, size=None): execute(*cmd) +def create_ploop_image(disk_format, path, size, fs_type): + """Create ploop image + + :param disk_format: Disk image format (as known by ploop) + :param path: Desired location of the ploop image + :param size: Desired size of ploop image. May be given as an int or + a string. If given as an int, it will be interpreted + as bytes. If it's a string, it should consist of a number + with an optional suffix ('K' for Kibibytes, + M for Mebibytes, 'G' for Gibibytes, 'T' for Tebibytes). + If no suffix is given, it will be interpreted as bytes. + :param fs_type: Filesystem type + """ + if not fs_type: + fs_type = CONF.default_ephemeral_format or \ + disk.FS_FORMAT_EXT4 + execute('mkdir', '-p', path) + disk_path = os.path.join(path, 'root.hds') + execute('ploop', 'init', '-s', size, '-f', disk_format, '-t', fs_type, + disk_path, run_as_root=True, check_exit_code=True) + # Add read access for all users, because "ploop init" creates + # disk with rw rights only for root. OpenStack user should have access + # to the disk to request info via "qemu-img info" + execute('chmod', '-R', 'a+r', path, + run_as_root=True, check_exit_code=True) + + def pick_disk_driver_name(hypervisor_version, is_block_dev=False): """Pick the libvirt primary backend driver name diff --git a/releasenotes/notes/bp-ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml b/releasenotes/notes/bp-ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml new file mode 100644 index 000000000000..d9b31353ddf0 --- /dev/null +++ b/releasenotes/notes/bp-ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml @@ -0,0 +1,3 @@ +--- +features: + - Virtuozzo hypervisor now supports ephemeral disks for containers.