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".