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:
Dmitry Guryanov 2015-02-02 20:12:04 +03:00 committed by Alexander Burluka
parent 5e1d37489d
commit 105c78efbe
4 changed files with 169 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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