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:
parent
4a02d9415f
commit
def785a23d
|
@ -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):
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -11336,6 +11336,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
|
|||
inst['key_data'] = 'ABCDEFG'
|
||||
inst['system_metadata'] = {}
|
||||
inst['metadata'] = {}
|
||||
inst['vm_mode'] = 'hvm'
|
||||
|
||||
inst.update(params)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue