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:
parent
ae753d9628
commit
b1d575f2ad
@ -252,6 +252,9 @@ sync: CommandFilter, sync, root
|
|||||||
ploop: RegExpFilter, ploop, root, ploop, restore-descriptor, .*
|
ploop: RegExpFilter, ploop, root, ploop, restore-descriptor, .*
|
||||||
prl_disk_tool: RegExpFilter, prl_disk_tool, root, prl_disk_tool, resize, --size, .*M$, --resize_partition, --hdd, .*
|
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'
|
# nova/virt/libvirt/utils.py: 'xend', 'status'
|
||||||
xend: CommandFilter, xend, root
|
xend: CommandFilter, xend, root
|
||||||
|
|
||||||
|
@ -34,6 +34,10 @@ def create_cow_image(backing_file, path):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def create_ploop_image(disk_format, path, size, fs_type):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_disk_size(path, format=None):
|
def get_disk_size(path, format=None):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -10761,7 +10761,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
|||||||
backend.mock_create_ephemeral.assert_called_once_with(
|
backend.mock_create_ephemeral.assert_called_once_with(
|
||||||
target=filename, ephemeral_size=100, fs_label='ephemeral0',
|
target=filename, ephemeral_size=100, fs_label='ephemeral0',
|
||||||
is_block_dev=mock.sentinel.is_block_dev, os_type='linux',
|
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(
|
backend.disks['disk.eph0'].cache.assert_called_once_with(
|
||||||
fetch_func=mock.ANY, context=self.context,
|
fetch_func=mock.ANY, context=self.context,
|
||||||
filename=filename, size=100 * units.Gi, ephemeral_size=mock.ANY,
|
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',
|
drvr._create_ephemeral('/dev/something', 20, 'myVol', 'linux',
|
||||||
is_block_dev=True)
|
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):
|
def test_create_swap_default(self):
|
||||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
|
||||||
self.mox.StubOutWithMock(utils, 'execute')
|
self.mox.StubOutWithMock(utils, 'execute')
|
||||||
|
@ -1710,6 +1710,13 @@ class PloopTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||||||
|
|
||||||
self.mox.VerifyAll()
|
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):
|
def test_prealloc_image(self):
|
||||||
self.flags(preallocate_images='space')
|
self.flags(preallocate_images='space')
|
||||||
fake_processutils.fake_execute_clear_log()
|
fake_processutils.fake_execute_clear_log()
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import ddt
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -39,6 +40,7 @@ from nova.virt.libvirt import utils as libvirt_utils
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class LibvirtUtilsTestCase(test.NoDBTestCase):
|
class LibvirtUtilsTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
@mock.patch('nova.utils.execute')
|
@mock.patch('nova.utils.execute')
|
||||||
@ -334,6 +336,32 @@ ID TAG VM SIZE DATE VM CLOCK
|
|||||||
'/the/new/cow'),)]
|
'/the/new/cow'),)]
|
||||||
self.assertEqual(expected_args, mock_execute.call_args_list)
|
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):
|
def test_pick_disk_driver_name(self):
|
||||||
type_map = {'kvm': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']),
|
type_map = {'kvm': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']),
|
||||||
'qemu': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']),
|
'qemu': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']),
|
||||||
|
@ -2883,8 +2883,16 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_ephemeral(target, ephemeral_size,
|
def _create_ephemeral(target, ephemeral_size,
|
||||||
fs_label, os_type, is_block_dev=False,
|
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 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)
|
libvirt_utils.create_image('raw', target, '%dG' % ephemeral_size)
|
||||||
|
|
||||||
# Run as root only for block devices.
|
# 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(
|
file_extension = disk_api.get_file_extension_for_os_type(
|
||||||
os_type_with_default)
|
os_type_with_default)
|
||||||
|
|
||||||
|
vm_mode = fields.VMMode.get_from_instance(instance)
|
||||||
ephemeral_gb = instance.flavor.ephemeral_gb
|
ephemeral_gb = instance.flavor.ephemeral_gb
|
||||||
if 'disk.local' in disk_mapping:
|
if 'disk.local' in disk_mapping:
|
||||||
disk_image = image('disk.local')
|
disk_image = image('disk.local')
|
||||||
fn = functools.partial(self._create_ephemeral,
|
fn = functools.partial(self._create_ephemeral,
|
||||||
fs_label='ephemeral0',
|
fs_label='ephemeral0',
|
||||||
os_type=instance.os_type,
|
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)
|
fname = "ephemeral_%s_%s" % (ephemeral_gb, file_extension)
|
||||||
size = ephemeral_gb * units.Gi
|
size = ephemeral_gb * units.Gi
|
||||||
disk_image.cache(fetch_func=fn,
|
disk_image.cache(fetch_func=fn,
|
||||||
@ -3086,7 +3096,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
fn = functools.partial(self._create_ephemeral,
|
fn = functools.partial(self._create_ephemeral,
|
||||||
fs_label='ephemeral%d' % idx,
|
fs_label='ephemeral%d' % idx,
|
||||||
os_type=instance.os_type,
|
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
|
size = eph['size'] * units.Gi
|
||||||
fname = "ephemeral_%s_%s" % (eph['size'], file_extension)
|
fname = "ephemeral_%s_%s" % (eph['size'], file_extension)
|
||||||
disk_image.cache(fetch_func=fn,
|
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_mapping = driver.block_device_info_get_mapping(
|
||||||
block_device_info)
|
block_device_info)
|
||||||
mount_rootfs = CONF.libvirt.virt_type == "lxc"
|
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:
|
if mount_rootfs:
|
||||||
fs = vconfig.LibvirtConfigGuestFilesys()
|
fs = vconfig.LibvirtConfigGuestFilesys()
|
||||||
fs.source_type = "mount"
|
fs.source_type = "mount"
|
||||||
@ -3531,6 +3555,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
if 'disk' in disk_mapping:
|
if 'disk' in disk_mapping:
|
||||||
fs = self._get_guest_fs_config(instance, "disk")
|
fs = self._get_guest_fs_config(instance, "disk")
|
||||||
devices.append(fs)
|
devices.append(fs)
|
||||||
|
devices = devices + _get_ephemeral_devices()
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if rescue:
|
if rescue:
|
||||||
@ -3562,14 +3587,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||||||
instance.default_ephemeral_device = (
|
instance.default_ephemeral_device = (
|
||||||
block_device.prepend_dev(disklocal.target_dev))
|
block_device.prepend_dev(disklocal.target_dev))
|
||||||
|
|
||||||
for idx, eph in enumerate(
|
devices = devices + _get_ephemeral_devices()
|
||||||
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)
|
|
||||||
|
|
||||||
if 'disk.swap' in disk_mapping:
|
if 'disk.swap' in disk_mapping:
|
||||||
diskswap = self._get_guest_disk_config(instance,
|
diskswap = self._get_guest_disk_config(instance,
|
||||||
|
@ -1026,11 +1026,18 @@ class Ploop(Image):
|
|||||||
|
|
||||||
self.resolve_driver_format()
|
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):
|
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)
|
@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")
|
image_path = os.path.join(target, "root.hds")
|
||||||
libvirt_utils.copy_image(base, image_path)
|
libvirt_utils.copy_image(base, image_path)
|
||||||
utils.execute('ploop', 'restore-descriptor', '-f', self.pcs_format,
|
utils.execute('ploop', 'restore-descriptor', '-f', self.pcs_format,
|
||||||
@ -1038,7 +1045,28 @@ class Ploop(Image):
|
|||||||
if size:
|
if size:
|
||||||
self.resize_image(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:
|
if CONF.force_raw_images:
|
||||||
self.pcs_format = "raw"
|
self.pcs_format = "raw"
|
||||||
else:
|
else:
|
||||||
@ -1058,19 +1086,8 @@ class Ploop(Image):
|
|||||||
image_id=kwargs["image_id"],
|
image_id=kwargs["image_id"],
|
||||||
reason=reason)
|
reason=reason)
|
||||||
|
|
||||||
if not os.path.exists(base):
|
with fileutils.remove_path_on_error(self.path, remove=remove_func):
|
||||||
prepare_template(target=base, *args, **kwargs)
|
_copy_ploop_image(base, self.path, size)
|
||||||
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)
|
|
||||||
|
|
||||||
def resize_image(self, size):
|
def resize_image(self, size):
|
||||||
image = imgmodel.LocalFileImage(self.path, imgmodel.FORMAT_PLOOP)
|
image = imgmodel.LocalFileImage(self.path, imgmodel.FORMAT_PLOOP)
|
||||||
|
@ -32,6 +32,7 @@ from nova.i18n import _LI
|
|||||||
from nova.i18n import _LW
|
from nova.i18n import _LW
|
||||||
from nova.objects import fields as obj_fields
|
from nova.objects import fields as obj_fields
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
from nova.virt.disk import api as disk
|
||||||
from nova.virt import images
|
from nova.virt import images
|
||||||
from nova.virt.libvirt import config as vconfig
|
from nova.virt.libvirt import config as vconfig
|
||||||
from nova.virt.libvirt.volume import remotefs
|
from nova.virt.libvirt.volume import remotefs
|
||||||
@ -98,6 +99,33 @@ def create_cow_image(backing_file, path, size=None):
|
|||||||
execute(*cmd)
|
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):
|
def pick_disk_driver_name(hypervisor_version, is_block_dev=False):
|
||||||
"""Pick the libvirt primary backend driver name
|
"""Pick the libvirt primary backend driver name
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Virtuozzo hypervisor now supports ephemeral disks for containers.
|
Loading…
Reference in New Issue
Block a user