diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index 5a486a011f..e4718486b7 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 bb30bdb1e7..5da52a3f4b 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 169bf438bb..c386042254 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 21271922ad..892ff0afa9 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 7612d8b4dc..9fb350e95b 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 ffcef901eb..7564090cff 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 06c739b0e0..8899229807 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 67b445400a..0392e21202 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 0000000000..d9b31353dd --- /dev/null +++ b/releasenotes/notes/bp-ephemeral-disk-ploop-a9b3af1f36ae42ed.yaml @@ -0,0 +1,3 @@ +--- +features: + - Virtuozzo hypervisor now supports ephemeral disks for containers.