libvirt: add ploop disks format support
The way how PCS works with disks slightly differs from how qemu does. In PCS each disk has metadata, stored in a separate xml file. The most important thing in this metadata is paths and types of base image and deltas. The name of xml file is fixed (DiskDescriptor.xml) and usually all images are stored in the same directory. Images are being downloaded from glance to cache in a form of files. So imagecache will work in the same way as with Raw image type. Here is and example of libvirt's disk config: <disk type='file' device='disk'> <driver type='ploop'/> <source file='/path/to/dir/with/xml'/> <target dev='sda' bus='sata'/> </disk> Since you need to provide a directory with xml file to libvirt, separate class is needed imagebackend.py for working with such images. This patch introduces class Ploop which are similar to Raw, it copies base image to the instance's dir and prepares it for starting VM or container from this image. If force_raw_images is false, then only raw and ploop image types allowed. If force_raw_images is true, then all image types, which are supported by qemu-img can be used. Partially implements blueprint pcs-support Change-Id: I9c2a0da52dbda7d45f749f9d42b2760a0d2e790f
This commit is contained in:
parent
5e1d37489d
commit
105c78efbe
|
@ -227,3 +227,5 @@ cp: CommandFilter, cp, root
|
|||
# nova/virt/xenapi/vm_utils.py:
|
||||
sync: CommandFilter, sync, root
|
||||
|
||||
# nova/virt/libvirt/imagebackend.py:
|
||||
ploop: CommandFilter, ploop, root
|
||||
|
|
|
@ -11521,6 +11521,44 @@ Active: 8381604 kB
|
|||
return_value=1002012):
|
||||
driver.init_host('wibble')
|
||||
|
||||
def test_get_guest_config_parallels_vm(self):
|
||||
self.flags(virt_type='parallels', group='libvirt')
|
||||
self.flags(images_type='ploop', group='libvirt')
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
instance_ref = objects.Instance(**self.test_instance)
|
||||
flavor = instance_ref.get_flavor()
|
||||
flavor.extra_specs = {}
|
||||
|
||||
image_meta = {}
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref,
|
||||
image_meta)
|
||||
|
||||
cfg = drvr._get_guest_config(instance_ref,
|
||||
_fake_network_info(self.stubs, 1),
|
||||
None, disk_info, flavor=flavor)
|
||||
self.assertEqual("parallels", cfg.virt_type)
|
||||
self.assertEqual(instance_ref["uuid"], cfg.uuid)
|
||||
self.assertEqual(2 * units.Mi, cfg.memory)
|
||||
self.assertEqual(1, cfg.vcpus)
|
||||
self.assertEqual(vm_mode.HVM, cfg.os_type)
|
||||
self.assertIsNone(cfg.os_root)
|
||||
self.assertEqual(6, len(cfg.devices))
|
||||
self.assertIsInstance(cfg.devices[0],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertEqual(cfg.devices[0].driver_format, "ploop")
|
||||
self.assertIsInstance(cfg.devices[1],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[2],
|
||||
vconfig.LibvirtConfigGuestInterface)
|
||||
self.assertIsInstance(cfg.devices[3],
|
||||
vconfig.LibvirtConfigGuestInput)
|
||||
self.assertIsInstance(cfg.devices[4],
|
||||
vconfig.LibvirtConfigGuestGraphics)
|
||||
self.assertIsInstance(cfg.devices[5],
|
||||
vconfig.LibvirtConfigGuestVideo)
|
||||
|
||||
|
||||
class HostStateTestCase(test.NoDBTestCase):
|
||||
|
||||
|
|
|
@ -1249,6 +1249,74 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
|
|||
self.assertEqual(image.path, rbd_path)
|
||||
|
||||
|
||||
class PloopTestCase(_ImageTestCase, test.NoDBTestCase):
|
||||
SIZE = 1024
|
||||
|
||||
def setUp(self):
|
||||
self.image_class = imagebackend.Ploop
|
||||
super(PloopTestCase, self).setUp()
|
||||
self.utils = imagebackend.utils
|
||||
self.stubs.Set(imagebackend.Ploop, 'get_disk_size', lambda a, b: 2048)
|
||||
|
||||
def prepare_mocks(self):
|
||||
fn = self.mox.CreateMockAnything()
|
||||
self.mox.StubOutWithMock(imagebackend.utils.synchronized,
|
||||
'__call__')
|
||||
self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image')
|
||||
self.mox.StubOutWithMock(self.utils, 'execute')
|
||||
return fn
|
||||
|
||||
def test_cache(self):
|
||||
self.mox.StubOutWithMock(os.path, 'exists')
|
||||
if self.OLD_STYLE_INSTANCE_PATH:
|
||||
os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
|
||||
os.path.exists(self.TEMPLATE_DIR).AndReturn(False)
|
||||
os.path.exists(self.PATH).AndReturn(False)
|
||||
os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
|
||||
fn = self.mox.CreateMockAnything()
|
||||
fn(target=self.TEMPLATE_PATH)
|
||||
self.mox.StubOutWithMock(imagebackend.fileutils, 'ensure_tree')
|
||||
imagebackend.fileutils.ensure_tree(self.TEMPLATE_DIR)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
image = self.image_class(self.INSTANCE, self.NAME)
|
||||
self.mock_create_image(image)
|
||||
image.cache(fn, self.TEMPLATE)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_create_image(self):
|
||||
fn = self.prepare_mocks()
|
||||
fn(target=self.TEMPLATE_PATH, max_size=2048, image_id=None)
|
||||
img_path = os.path.join(self.PATH, "root.hds")
|
||||
imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH, img_path)
|
||||
self.utils.execute("ploop", "restore-descriptor", "-f", "raw",
|
||||
self.PATH, img_path)
|
||||
self.utils.execute("ploop", "grow", '-s', "2K",
|
||||
os.path.join(self.PATH, "DiskDescriptor.xml"),
|
||||
run_as_root=True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
image = self.image_class(self.INSTANCE, self.NAME)
|
||||
image.create_image(fn, self.TEMPLATE_PATH, 2048, image_id=None)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_prealloc_image(self):
|
||||
self.flags(preallocate_images='space')
|
||||
fake_processutils.fake_execute_clear_log()
|
||||
fake_processutils.stub_out_processutils_execute(self.stubs)
|
||||
image = self.image_class(self.INSTANCE, self.NAME)
|
||||
|
||||
def fake_fetch(target, *args, **kwargs):
|
||||
return
|
||||
|
||||
self.stubs.Set(os.path, 'exists', lambda _: True)
|
||||
self.stubs.Set(image, 'check_image_exists', lambda: True)
|
||||
|
||||
image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
|
||||
|
||||
|
||||
class BackendTestCase(test.NoDBTestCase):
|
||||
INSTANCE = {'name': 'fake-instance',
|
||||
'uuid': uuidutils.generate_uuid()}
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
import abc
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.serialization import jsonutils
|
||||
|
@ -738,6 +740,64 @@ class Rbd(Image):
|
|||
reason=reason)
|
||||
|
||||
|
||||
class Ploop(Image):
|
||||
def __init__(self, instance=None, disk_name=None, path=None):
|
||||
super(Ploop, self).__init__("file", "ploop", is_block_dev=False)
|
||||
|
||||
self.path = (path or
|
||||
os.path.join(libvirt_utils.get_instance_path(instance),
|
||||
disk_name))
|
||||
self.resolve_driver_format()
|
||||
|
||||
def create_image(self, prepare_template, base, size, *args, **kwargs):
|
||||
filename = os.path.split(base)[-1]
|
||||
|
||||
@utils.synchronized(filename, external=True, lock_path=self.lock_path)
|
||||
def create_ploop_image(base, target, size):
|
||||
image_path = os.path.join(target, "root.hds")
|
||||
libvirt_utils.copy_image(base, image_path)
|
||||
utils.execute('ploop', 'restore-descriptor', '-f', self.pcs_format,
|
||||
target, image_path)
|
||||
if size:
|
||||
dd_path = os.path.join(self.path, "DiskDescriptor.xml")
|
||||
utils.execute('ploop', 'grow', '-s', '%dK' % (size >> 10),
|
||||
dd_path, run_as_root=True)
|
||||
|
||||
if not os.path.exists(self.path):
|
||||
if CONF.force_raw_images:
|
||||
self.pcs_format = "raw"
|
||||
else:
|
||||
image_meta = IMAGE_API.get(kwargs["context"],
|
||||
kwargs["image_id"])
|
||||
format = image_meta.get("disk_format")
|
||||
if format == "ploop":
|
||||
self.pcs_format = "expanded"
|
||||
elif format == "raw":
|
||||
self.pcs_format = "raw"
|
||||
else:
|
||||
reason = _("PCS doesn't support images in %s format."
|
||||
" You should either set force_raw_images=True"
|
||||
" in config or upload an image in ploop"
|
||||
" or raw format.") % format
|
||||
raise exception.ImageUnacceptable(
|
||||
image_id=kwargs["image_id"],
|
||||
reason=reason)
|
||||
|
||||
if not os.path.exists(base):
|
||||
prepare_template(target=base, max_size=size, *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)
|
||||
|
||||
|
||||
class Backend(object):
|
||||
def __init__(self, use_cow):
|
||||
self.BACKEND = {
|
||||
|
@ -745,6 +805,7 @@ class Backend(object):
|
|||
'qcow2': Qcow2,
|
||||
'lvm': Lvm,
|
||||
'rbd': Rbd,
|
||||
'ploop': Ploop,
|
||||
'default': Qcow2 if use_cow else Raw
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue