Add config drive support for Virtuozzo containers

With this change, we're defining new setup methodology
for container based virt (parallels now, and in future LXC too).
In such environments the *host* container technology will be
responsible for mounting the config drive image inside the guest
filesystem. So when the guest image boots, it will see the config
drive data immediately at the location defined by cloud-init.
The only demand for existing images is having cloud-init turned on.

To support config drive in Virtuozzo (former PCS) containers,
we use ploop block device. Actions with ploop device are performed
via ploop CLI tool and newly created device is mounted into
/var/lib/cloud/seed/config_drive on container's filesystem.
This destination recognised by cloud-init as a source of data.

(Ploop device description: https://openvz.org/Ploop)

Co-Authored-By: Maxim Nestratov <mnestratov@parallels.com>

Change-Id: Ib21ab954a08027fb1f2aec13c2d95b5f080eccce
Implements: blueprint config-drive-support-for-virtuozzo
This commit is contained in:
Alexander Burluka 2015-01-21 16:45:00 -05:00 committed by Maxim Nestratov
parent 4a02d9415f
commit def785a23d
9 changed files with 162 additions and 20 deletions

View File

@ -1414,8 +1414,13 @@ class ConfigDriveMountFailed(NovaException):
class ConfigDriveUnknownFormat(NovaException):
msg_fmt = _("Unknown config drive format %(format)s. Select one of "
"iso9660 or vfat.")
msg_fmt = _("Unknown config drive format %(format)s for %(os_type)s "
"os type. Valid options are: iso9660, vfat or None.")
class ConfigDriveUnsupportedFormat(NovaException):
msg_fmt = _("Unsupported config drive format %(format)s "
"for %(image_type)s image type. Image path: %(image_path)")
class InterfaceAttachFailed(Invalid):

View File

@ -14,10 +14,13 @@
# under the License.
import os
import tempfile
import fixtures
import mock
import os
import shutil
import tempfile
import uuid
from mox3 import mox
from oslo_config import cfg
@ -93,6 +96,35 @@ class ConfigDriveTestCase(test.NoDBTestCase):
if imagefile:
fileutils.delete_if_exists(imagefile)
def _create_ext4_ploop(self, calls):
imagefile = None
with mock.patch.object(utils, 'execute', return_value=('', '')) as ex:
with configdrive.ConfigDriveBuilder(FakeInstanceMD()) as c:
imagefile = "/tmp/cd_ext4_" + str(uuid.uuid4())[:8]
c.make_drive(imagefile, image_type='ploop')
ex.assert_has_calls(calls)
return imagefile
def test_create_configdrive_ext4_ploop(self):
self.flags(config_drive_format='ext4')
self.flags(virt_type='parallels', group='libvirt')
def fake_trycmd(*args, **kwargs):
return None, None
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd', fake_trycmd))
calls = [
mock.call('ploop', 'init', '-s', mock.ANY,
'-t', 'ext4', mock.ANY,
run_as_root=True, attempts=1),
mock.call('chown', '-R', mock.ANY,
mock.ANY, run_as_root=True),
mock.call('ploop', 'umount',
mock.ANY, run_as_root=True)
]
imagefile = self._create_ext4_ploop(calls)
if imagefile:
shutil.rmtree(imagefile)
def test_config_drive_required_by_image_property(self):
inst = fake_instance.fake_instance_obj(context.get_admin_context())
inst.config_drive = ''

View File

@ -19,6 +19,7 @@ import mock
from nova import block_device
from nova.compute import arch
from nova.compute import vm_mode
from nova import context
from nova import exception
from nova import objects
@ -679,6 +680,17 @@ class LibvirtBlockInfoTest(test.NoDBTestCase):
config_drive_type = blockinfo.get_config_drive_type()
self.assertEqual('disk', config_drive_type)
def test_get_config_drive_type_ploop(self):
self.flags(config_drive_format=None)
self.flags(virt_type='parallels', group='libvirt')
config_drive_type = blockinfo.get_config_drive_type(vm_mode.EXE)
self.assertEqual('fs', config_drive_type)
def test_get_config_drive_type_improper_value(self):
self.flags(config_drive_format='test')
self.assertRaises(exception.ConfigDriveUnknownFormat,
blockinfo.get_config_drive_type)
def test_get_info_from_bdm(self):
bdms = [{'device_name': '/dev/vds', 'device_type': 'disk',
'disk_bus': 'usb', 'swap_size': 4},

View File

@ -11336,6 +11336,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
inst['key_data'] = 'ABCDEFG'
inst['system_metadata'] = {}
inst['metadata'] = {}
inst['vm_mode'] = 'hvm'
inst.update(params)

View File

@ -55,6 +55,12 @@ CONF.register_opts(configdrive_opts)
# Config drives are 64mb, if we can't size to the exact size of the data
CONFIGDRIVESIZE_BYTES = 64 * units.Mi
FS_FORMAT_VFAT = 'vfat'
FS_FORMAT_ISO9660 = 'iso9660'
IMAGE_TYPE_RAW = 'raw'
IMAGE_TYPE_PLOOP = 'ploop'
class ConfigDriveBuilder(object):
"""Build config drives, optionally as a context manager."""
@ -150,23 +156,80 @@ class ConfigDriveBuilder(object):
if mounted:
utils.execute('umount', mountdir, run_as_root=True)
def make_drive(self, path):
def _make_ext4_ploop(self, path, tmpdir):
"""ploop is a disk loopback block device, that is used in
Parallels(OpenVZ) containers. It is similiar to Linux loop
device but prevents double caching of data in memory and
supports snapshots and some other effeciency benefits. Adding
ploop is a natural way to add disk device to VZ containers.
Ploop device has its own image format. It contains specific
partition table with one ext4 partition.
"""
os.mkdir(path)
utils.execute('ploop',
'init',
'-s', CONFIGDRIVESIZE_BYTES,
'-t', 'ext4',
path + '/disk.config.hds',
attempts=1,
run_as_root=True)
with utils.tempdir() as mountdir:
mounted = False
try:
_, err = utils.trycmd(
'ploop', 'mount',
'-m', mountdir,
'-t', 'ext4',
path + '/DiskDescriptor.xml',
run_as_root=True)
if os.path.exists(mountdir):
utils.execute('chown', '-R',
'%(u)d:%(g)d' % {'u': os.getuid(),
'g': os.getgid()},
mountdir,
run_as_root=True)
mounted = True
for ent in os.listdir(tmpdir):
shutil.copytree(os.path.join(tmpdir, ent),
os.path.join(mountdir, ent))
finally:
if mounted:
utils.execute('ploop', 'umount',
path + '/disk.config.hds', run_as_root=True)
def make_drive(self, path, image_type=IMAGE_TYPE_RAW):
"""Make the config drive.
:param path: the path to place the config drive image at
:param image_type: host side image format
:raises ProcessExecuteError if a helper process has failed.
"""
fs_format = CONF.config_drive_format
if fs_format is None:
if image_type == IMAGE_TYPE_RAW:
fs_format = FS_FORMAT_ISO9660
with utils.tempdir() as tmpdir:
self._write_md_files(tmpdir)
if CONF.config_drive_format == 'iso9660':
self._make_iso9660(path, tmpdir)
elif CONF.config_drive_format == 'vfat':
self._make_vfat(path, tmpdir)
if image_type == IMAGE_TYPE_RAW:
if fs_format not in (FS_FORMAT_VFAT, FS_FORMAT_ISO9660):
raise exception.ConfigDriveUnsupportedFormat(
format=fs_format,
image_type=image_type,
image_path=path)
elif fs_format == FS_FORMAT_ISO9660:
self._make_iso9660(path, tmpdir)
elif fs_format == FS_FORMAT_VFAT:
self._make_vfat(path, tmpdir)
elif image_type == IMAGE_TYPE_PLOOP:
self._make_ext4_ploop(path, tmpdir)
else:
raise exception.ConfigDriveUnknownFormat(
format=CONF.config_drive_format)
raise exception.ConfigDriveUnsupportedFormat(
format=fs_format,
image_type=image_type,
image_path=path)
def cleanup(self):
if self.imagefile:

View File

@ -372,7 +372,7 @@ class VMOps(object):
def _create_config_drive(self, instance, injected_files, admin_password,
network_info):
if CONF.config_drive_format != 'iso9660':
if CONF.config_drive_format not in ('iso9660', None):
raise vmutils.UnsupportedConfigDriveFormatException(
_('Invalid config_drive_format "%s"') %
CONF.config_drive_format)

View File

@ -265,7 +265,7 @@ def get_disk_bus_for_device_type(virt_type,
elif virt_type == "parallels":
if device_type == "cdrom":
return "ide"
elif device_type == "disk":
elif device_type in ("disk", "fs"):
return "sata"
else:
# If virt-type not in list then it is unsupported
@ -341,11 +341,15 @@ def get_eph_disk(index):
return 'disk.eph' + str(index)
def get_config_drive_type():
def get_config_drive_type(os_type=None):
"""Determine the type of config drive.
If config_drive_format is set to iso9660 then the config drive will
be 'cdrom', otherwise 'disk'.
Config drive will be:
'cdrom' in case of config_drive is set to iso9660;
'disk' in case of config_drive is set to vfat;
'fs' in case of os_type is EXE and virt_type is parallels;
Autodetected from (cdrom, disk, fs) in case of config_drive is None;
Otherwise, an exception of unknown format will be thrown.
Returns a string indicating the config drive type.
"""
@ -354,9 +358,22 @@ def get_config_drive_type():
config_drive_type = 'cdrom'
elif CONF.config_drive_format == 'vfat':
config_drive_type = 'disk'
elif CONF.config_drive_format is None:
if CONF.libvirt.virt_type == 'parallels':
if os_type == vm_mode.HVM:
config_drive_type = 'cdrom'
elif os_type == vm_mode.EXE:
config_drive_type = 'fs'
else:
raise exception.ConfigDriveUnknownFormat(
format=CONF.config_drive_format,
os_type=os_type)
else:
config_drive_type = 'cdrom'
else:
raise exception.ConfigDriveUnknownFormat(
format=CONF.config_drive_format)
format=CONF.config_drive_format,
os_type=os_type)
return config_drive_type

View File

@ -2851,7 +2851,13 @@ class LibvirtDriver(driver.ComputeDriver):
{'path': configdrive_path}, instance=instance)
try:
cdb.make_drive(configdrive_path)
os_type = vm_mode.get_from_instance(instance)
if (os_type == vm_mode.EXE and
CONF.libvirt.virt_type == "parallels"):
cdb.make_drive(configdrive_path,
configdrive.IMAGE_TYPE_PLOOP)
else:
cdb.make_drive(configdrive_path)
except processutils.ProcessExecutionError as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Creating config drive failed '
@ -3164,6 +3170,12 @@ class LibvirtDriver(driver.ComputeDriver):
elif os_type == vm_mode.EXE and CONF.libvirt.virt_type == "parallels":
fs = self._get_guest_fs_config(instance, "disk")
devices.append(fs)
if 'disk.config' in disk_mapping:
disk_config_image = self.image_backend.image(instance,
"disk.config",
"ploop")
devices.append(disk_config_image.libvirt_fs_info(
"/var/lib/cloud/seed/config_drive", "ploop"))
else:
if rescue:

View File

@ -691,7 +691,7 @@ class VMwareVMOps(object):
def _create_config_drive(self, instance, injected_files, admin_password,
data_store_name, dc_name, upload_folder, cookies):
if CONF.config_drive_format != 'iso9660':
if CONF.config_drive_format not in ('iso9660', None):
reason = (_('Invalid config_drive_format "%s"') %
CONF.config_drive_format)
raise exception.InstancePowerOnFailure(reason=reason)