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
This commit is contained in:
Mikhail Feoktistov 2016-09-13 06:08:57 -04:00
parent ae753d9628
commit b1d575f2ad
9 changed files with 148 additions and 28 deletions

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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()

View File

@ -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']),

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,3 @@
---
features:
- Virtuozzo hypervisor now supports ephemeral disks for containers.