libvirt: virtuozzo instance resize support

Adapt "nova resize" code to support Virtuozzo ploop disks.
As far as ploop disks are in fact directories we add '-r' argument
to all utilities that deal with instance' disks such as cp, rsync and scp.
Thus we copy disks universally whether they are folders or files.

Also using "prl_disk_tool" instead of "qemu-img" is better for ploop images
because it resizes guest filesystem as well.

We can't resize disks from guest OS in containers,
because they are not allowed to write directly to block device.
ploop tool can resize partition table and internal filesystem,
but only for container's disks. Such disks must have only one partition
with ext filesystem.

prl_disk_tool can resize disks with internal filesystems
and doesn't require any special layout so it can resize disks
for virtual machines.  So it's better to use this tool instead of ploop.

Also we make compute.filters more strict
We call "ploop" only with "restore-descriptor" argument
And we set disk size in megabytes for prl_disk_tool

Co-Authored-By: Dmitry Guryanov <dguryanov@parallels.com>
Depends-On: I04c4379459c2fc1fd4801ec2aad53d0f6053b6d6
Change-Id: I38dbf73beb01fe1939ddca63fbfedbec1dc3c826
Implements: blueprint virtuozzo-instance-resize-support
This commit is contained in:
Mikhail Feoktistov 2015-06-11 14:42:08 +03:00 committed by Maxim Nestratov
parent a5dae110fb
commit d4aa455d53
12 changed files with 62 additions and 19 deletions

View File

@ -385,8 +385,10 @@ driver-impl-vmware=complete
driver-impl-hyperv=complete
driver-impl-ironic=partial
driver-notes-ironic=Only certain ironic drivers support this
driver-impl-libvirt-vz-vm=missing
driver-impl-libvirt-vz-ct=missing
driver-impl-libvirt-vz-vm=complete
driver-notes-vz-vm=Resizing Virtuozzo instances implies guest filesystem resize also
driver-impl-libvirt-vz-ct=complete
driver-notes-vz-ct=Resizing Virtuozzo instances implies guest filesystem resize also
[operation.resume]
title=Restore instance

View File

@ -246,7 +246,8 @@ cp: CommandFilter, cp, root
sync: CommandFilter, sync, root
# nova/virt/libvirt/imagebackend.py:
ploop: CommandFilter, ploop, root
ploop: RegExpFilter, ploop, root, ploop, restore-descriptor, .*
prl_disk_tool: RegExpFilter, prl_disk_tool, root, prl_disk_tool, resize, --size, .*M$, --resize_partition, --hdd, .*
# nova/virt/libvirt/utils.py: 'xend', 'status'
xend: CommandFilter, xend, root

View File

@ -18,6 +18,7 @@ import tempfile
import fixtures
import mock
from oslo_concurrency import processutils
from oslo_utils import units
from nova import test
from nova import utils
@ -176,6 +177,24 @@ class APITestCase(test.NoDBTestCase):
imgsize)
self.assertFalse(mock_extendable.called)
@mock.patch.object(api, 'can_resize_image', return_value=True)
@mock.patch.object(utils, 'execute')
def test_extend_ploop(self, mock_execute, mock_can_resize_image):
imgfile = tempfile.NamedTemporaryFile()
self.addCleanup(imgfile.close)
imgsize = 10 * units.Gi
imgsize_mb = str(imgsize // units.Mi) + 'M'
image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_PLOOP)
api.extend(image, imgsize)
mock_can_resize_image.assert_called_once_with(image.path,
imgsize)
mock_execute.assert_called_once_with('prl_disk_tool', 'resize',
'--size', imgsize_mb,
'--resize_partition',
'--hdd', imgfile,
run_as_root=True)
def test_extend_raw_success(self):
imgfile = tempfile.NamedTemporaryFile()
self.addCleanup(imgfile.close)

View File

@ -1590,6 +1590,7 @@ class PloopTestCase(_ImageTestCase, test.NoDBTestCase):
'__call__')
self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image')
self.mox.StubOutWithMock(self.utils, 'execute')
self.mox.StubOutWithMock(imagebackend.disk, 'extend')
return fn
def test_cache(self):
@ -1619,9 +1620,9 @@ class PloopTestCase(_ImageTestCase, test.NoDBTestCase):
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)
image = imgmodel.LocalFileImage(self.PATH, imgmodel.FORMAT_PLOOP)
imagebackend.disk.extend(image, 2048)
self.mox.ReplayAll()
image = self.image_class(self.INSTANCE, self.NAME)

View File

@ -44,7 +44,7 @@ class LibvirtUtilsTestCase(test.NoDBTestCase):
@mock.patch('nova.utils.execute')
def test_copy_image_local(self, mock_execute):
libvirt_utils.copy_image('src', 'dest')
mock_execute.assert_called_once_with('cp', 'src', 'dest')
mock_execute.assert_called_once_with('cp', '-r', 'src', 'dest')
@mock.patch('nova.virt.libvirt.volume.remotefs.SshDriver.copy_file')
def test_copy_image_remote_ssh(self, mock_rem_fs_remove):

View File

@ -166,7 +166,7 @@ class RemoteFSTestCase(test.NoDBTestCase):
remotefs.RsyncDriver().copy_file('1.2.3.4:/home/star_wars',
'/home/favourite', None, None,
compression=True)
mock_execute.assert_called_once_with('rsync', '--sparse',
mock_execute.assert_called_once_with('rsync', '-r', '--sparse',
'1.2.3.4:/home/star_wars',
'/home/favourite',
'--compress',
@ -178,7 +178,7 @@ class RemoteFSTestCase(test.NoDBTestCase):
remotefs.RsyncDriver().copy_file('1.2.3.4:/home/star_wars',
'/home/favourite', None, None,
compression=False)
mock_execute.assert_called_once_with('rsync', '--sparse',
mock_execute.assert_called_once_with('rsync', '-r', '--sparse',
'1.2.3.4:/home/star_wars',
'/home/favourite',
on_completion=None,
@ -188,7 +188,7 @@ class RemoteFSTestCase(test.NoDBTestCase):
def test_remote_copy_file_ssh(self, mock_execute):
remotefs.SshDriver().copy_file('1.2.3.4:/home/SpaceOdyssey',
'/home/favourite', None, None, True)
mock_execute.assert_called_once_with('scp',
mock_execute.assert_called_once_with('scp', '-r',
'1.2.3.4:/home/SpaceOdyssey',
'/home/favourite',
on_completion=None,

View File

@ -33,6 +33,7 @@ if os.name != 'nt':
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import units
import nova.conf
from nova import exception
@ -158,6 +159,16 @@ def extend(image, size):
if not isinstance(image, imgmodel.LocalImage):
return
if (image.format == imgmodel.FORMAT_PLOOP):
if not can_resize_image(image.path, size):
return
utils.execute('prl_disk_tool', 'resize',
'--size', '%dM' % (size // units.Mi),
'--resize_partition',
'--hdd', image.path, run_as_root=True)
return
if not can_resize_image(image.path, size):
return

View File

@ -20,10 +20,12 @@ from nova import exception
FORMAT_RAW = "raw"
FORMAT_QCOW2 = "qcow2"
FORMAT_PLOOP = "ploop"
ALL_FORMATS = [
FORMAT_RAW,
FORMAT_QCOW2,
FORMAT_PLOOP,
]

View File

@ -1024,9 +1024,7 @@ class Ploop(Image):
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)
self.resize_image(size)
if not os.path.exists(self.path):
if CONF.force_raw_images:
@ -1063,9 +1061,8 @@ class Ploop(Image):
create_ploop_image(base, self.path, size)
def resize_image(self, size):
dd_path = os.path.join(self.path, "DiskDescriptor.xml")
utils.execute('ploop', 'grow', '-s', '%dK' % (size >> 10), dd_path,
run_as_root=True)
image = imgmodel.LocalFileImage(self.path, imgmodel.FORMAT_PLOOP)
disk.extend(image, size)
def snapshot_extract(self, target, out_format):
img_path = os.path.join(self.path, "root.hds")

View File

@ -199,7 +199,8 @@ def copy_image(src, dest, host=None, receive=False,
# sparse files. I.E. holes will not be written to DEST,
# rather recreated efficiently. In addition, since
# coreutils 8.11, holes can be read efficiently too.
execute('cp', src, dest)
# we add '-r' argument because ploop disks are directories
execute('cp', '-r', src, dest)
else:
if receive:
src = "%s:%s" % (utils.safe_ip_format(host), src)

View File

@ -196,7 +196,8 @@ class SshDriver(RemoteFilesystemDriver):
on_execute=on_execute, on_completion=on_completion)
def copy_file(self, src, dst, on_execute, on_completion, compression):
utils.execute('scp', src, dst,
# As far as ploop disks are in fact directories we add '-r' argument
utils.execute('scp', '-r', src, dst,
on_execute=on_execute, on_completion=on_completion)
@ -324,7 +325,8 @@ class RsyncDriver(RemoteFilesystemDriver):
on_execute=on_execute, on_completion=on_completion)
def copy_file(self, src, dst, on_execute, on_completion, compression):
args = ['rsync', '--sparse', src, dst]
# As far as ploop disks are in fact directories we add '-r' argument
args = ['rsync', '-r', '--sparse', src, dst]
if compression:
args.append('--compress')
utils.execute(*args,

View File

@ -0,0 +1,7 @@
---
features:
- Virtuozzo ploop disks can be resized now during "nova resize".
upgrade:
- You must update the rootwrap configuration for the compute service
if you use ploop images, so that "ploop grow" filter is changed
to "prl_disk_tool resize".