From d4aa455d53c91c6dfebbf9a9850f7b6c3fef4545 Mon Sep 17 00:00:00 2001 From: Mikhail Feoktistov Date: Thu, 11 Jun 2015 14:42:08 +0300 Subject: [PATCH] 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 Depends-On: I04c4379459c2fc1fd4801ec2aad53d0f6053b6d6 Change-Id: I38dbf73beb01fe1939ddca63fbfedbec1dc3c826 Implements: blueprint virtuozzo-instance-resize-support --- doc/source/support-matrix.ini | 6 ++++-- etc/nova/rootwrap.d/compute.filters | 3 ++- nova/tests/unit/virt/disk/test_api.py | 19 +++++++++++++++++++ .../unit/virt/libvirt/test_imagebackend.py | 7 ++++--- nova/tests/unit/virt/libvirt/test_utils.py | 2 +- .../unit/virt/libvirt/volume/test_remotefs.py | 6 +++--- nova/virt/disk/api.py | 11 +++++++++++ nova/virt/image/model.py | 2 ++ nova/virt/libvirt/imagebackend.py | 9 +++------ nova/virt/libvirt/utils.py | 3 ++- nova/virt/libvirt/volume/remotefs.py | 6 ++++-- ...tance-resize-support-b523e6e8a0de0fbc.yaml | 7 +++++++ 12 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/bp-virtuozzo-instance-resize-support-b523e6e8a0de0fbc.yaml diff --git a/doc/source/support-matrix.ini b/doc/source/support-matrix.ini index 491aa30cf319..a3b701e3678f 100644 --- a/doc/source/support-matrix.ini +++ b/doc/source/support-matrix.ini @@ -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 diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index 00766318383b..944740ed3002 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -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 diff --git a/nova/tests/unit/virt/disk/test_api.py b/nova/tests/unit/virt/disk/test_api.py index d85a26c63278..fae342767397 100644 --- a/nova/tests/unit/virt/disk/test_api.py +++ b/nova/tests/unit/virt/disk/test_api.py @@ -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) diff --git a/nova/tests/unit/virt/libvirt/test_imagebackend.py b/nova/tests/unit/virt/libvirt/test_imagebackend.py index 6330ef76aeb3..26344ff897f0 100644 --- a/nova/tests/unit/virt/libvirt/test_imagebackend.py +++ b/nova/tests/unit/virt/libvirt/test_imagebackend.py @@ -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) diff --git a/nova/tests/unit/virt/libvirt/test_utils.py b/nova/tests/unit/virt/libvirt/test_utils.py index 5a9cbe879b5f..a4bc4726f524 100644 --- a/nova/tests/unit/virt/libvirt/test_utils.py +++ b/nova/tests/unit/virt/libvirt/test_utils.py @@ -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): diff --git a/nova/tests/unit/virt/libvirt/volume/test_remotefs.py b/nova/tests/unit/virt/libvirt/volume/test_remotefs.py index 3be6a1764b76..d7ccaabc261f 100644 --- a/nova/tests/unit/virt/libvirt/volume/test_remotefs.py +++ b/nova/tests/unit/virt/libvirt/volume/test_remotefs.py @@ -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, diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py index 7f699939c5a2..bc2921793080 100644 --- a/nova/virt/disk/api.py +++ b/nova/virt/disk/api.py @@ -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 diff --git a/nova/virt/image/model.py b/nova/virt/image/model.py index 4e8f46ed6980..971f7e9c0704 100644 --- a/nova/virt/image/model.py +++ b/nova/virt/image/model.py @@ -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, ] diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py index ad60205ec777..de06e4da9835 100644 --- a/nova/virt/libvirt/imagebackend.py +++ b/nova/virt/libvirt/imagebackend.py @@ -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") diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index f1fe2638aef8..ff811c221fc4 100644 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -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) diff --git a/nova/virt/libvirt/volume/remotefs.py b/nova/virt/libvirt/volume/remotefs.py index eafb56565a35..1d8d289a0166 100644 --- a/nova/virt/libvirt/volume/remotefs.py +++ b/nova/virt/libvirt/volume/remotefs.py @@ -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, diff --git a/releasenotes/notes/bp-virtuozzo-instance-resize-support-b523e6e8a0de0fbc.yaml b/releasenotes/notes/bp-virtuozzo-instance-resize-support-b523e6e8a0de0fbc.yaml new file mode 100644 index 000000000000..0f728f072c9f --- /dev/null +++ b/releasenotes/notes/bp-virtuozzo-instance-resize-support-b523e6e8a0de0fbc.yaml @@ -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".