From 202446cd37098db579aad7899174c033f1b9b921 Mon Sep 17 00:00:00 2001 From: Tiago Sousa Date: Fri, 10 Oct 2025 17:28:51 +0100 Subject: [PATCH] plugin: lvmplugin: add underlay functions --- src/PVE/Storage.pm | 28 ++++++++++++++++ src/PVE/Storage/LVMPlugin.pm | 65 +++++++++++++++++++++++++++++++----- src/PVE/Storage/Plugin.pm | 29 +++++++++++++++- 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index ebbaf45..e0b4ba8 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -480,6 +480,34 @@ sub volume_resize { } } +sub volume_underlay_size_info { + my ($cfg, $volid, $timeout) = @_; + + my ($storeid, $volname) = parse_volume_id($volid, 1); + if ($storeid) { + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_underlay_size_info($scfg, $storeid, $volname, $timeout); + } else { + return 0; + } +} + +sub volume_underlay_resize { + my ($cfg, $volid, $size, $running, $backing_snap) = @_; + + my ($storeid, $volname) = parse_volume_id($volid, 1); + if ($storeid) { + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + return $plugin->volume_underlay_resize($scfg, $storeid, $volname, $size, $running, $backing_snap); + } elsif ($volid =~ m|^(/.+)$| && -e $volid) { + die "resize file/device '$volid' is not possible\n"; + } else { + die "unable to parse volume ID '$volid'\n"; + } +} + sub volume_rollback_is_possible { my ($cfg, $volid, $snap, $blockers) = @_; diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm index dc5e648..74366c3 100644 --- a/src/PVE/Storage/LVMPlugin.pm +++ b/src/PVE/Storage/LVMPlugin.pm @@ -400,6 +400,8 @@ sub options { tagged_only => { optional => 1 }, bwlimit => { optional => 1 }, 'snapshot-as-volume-chain' => { optional => 1 }, + chunksize => { optional => 1 }, + 'chunk-percentage' => { optional => 1 }, }; } @@ -939,6 +941,44 @@ sub volume_resize { $lvmsize = "${lvmsize}k"; my $path = $class->path($scfg, $volname); + lv_extend($class, $scfg, $storeid, $lvmsize, $path); + + if (!$running && $format eq 'qcow2') { + my $preallocation = PVE::Storage::Plugin::preallocation_cmd_opt($scfg, $format); + PVE::Storage::Common::qemu_img_resize($path, $format, $size, $preallocation, 10); + } + + return 1; +} + +sub volume_underlay_resize { + my ($class, $scfg, $storeid, $volname, $backing_snap) = @_; + + my ($format) = ($class->parse_volname($volname))[6]; + + my $path = $class->filesystem_path($scfg, $volname); + my $json = PVE::Storage::Common::qemu_img_info($path, undef, 10, 0); + my $json_decode = eval { decode_json($json) }; + if ($@) { + die "Can't decode qemu snapshot list. Invalid JSON: $@\n"; + } + + my $virtual_size = $json_decode->{'virtual-size'} / 1024; + + my $underlay_size = lv_size($path, 10); + + my $updated_underlay_size = ($underlay_size + $scfg->{chunksize}) / 1024; + $updated_underlay_size = calculate_lvm_size($virtual_size, $format, $backing_snap) + if $updated_underlay_size >= $virtual_size; + + my $lvmsize = "${updated_underlay_size}k"; + lv_extend($class, $scfg, $storeid, $lvmsize, $path); + + return $updated_underlay_size; +} + +sub lv_extend { + my ($class, $scfg, $storeid, $lvmsize, $path) = @_; my $cmd = ['/sbin/lvextend', '-L', $lvmsize, $path]; $class->cluster_lock_storage( @@ -949,13 +989,6 @@ sub volume_resize { run_command($cmd, errmsg => "error resizing volume '$path'"); }, ); - - if (!$running && $format eq 'qcow2') { - my $preallocation = PVE::Storage::Plugin::preallocation_cmd_opt($scfg, $format); - PVE::Storage::Common::qemu_img_resize($path, $format, $size, $preallocation, 10); - } - - return 1; } sub volume_size_info { @@ -966,6 +999,22 @@ sub volume_size_info { return PVE::Storage::Plugin::file_size_info($path, $timeout, $format) if $format eq 'qcow2'; + my $size = lv_size($path, $timeout); + return wantarray ? ($size, 'raw', 0, undef) : $size; +} + +sub volume_underlay_size_info { + my ($class, $scfg, $storeid, $volname, $timeout) = @_; + + my ($format) = ($class->parse_volname($volname))[6]; + my $path = $class->filesystem_path($scfg, $volname); + + return lv_size($path, $timeout); +} + +sub lv_size { + my ($path, $timeout) = @_; + my $cmd = [ '/sbin/lvs', '--separator', @@ -989,7 +1038,7 @@ sub volume_size_info { $size = int(shift); }, ); - return wantarray ? ($size, 'raw', 0, undef) : $size; + return $size; } sub volume_snapshot { diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index 2291d72..9bc60f2 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -228,6 +228,20 @@ my $defaultData = { default => 0, optional => 1, }, + chunksize => { + type => 'integer', + description => 'The chunksize in Bytes to define the write threshold' + . 'of thin disks on thick storage.', + default => 1073741824, # 1 GiB + optional => 1, + }, + 'chunk-percentage' => { + type => 'number', + description => 'The percentage of written disk to define the write' + . 'threshold.', + default => 0.5, + optional => 1, + }, }, }; @@ -1265,7 +1279,6 @@ sub volume_size_info { my $format = ($class->parse_volname($volname))[6]; my $path = $class->filesystem_path($scfg, $volname); return file_size_info($path, $timeout, $format); - } sub volume_resize { @@ -1285,6 +1298,20 @@ sub volume_resize { return undef; } +sub volume_underlay_size_info { + my ($class, $scfg, $storeid, $volname, $timeout) = @_; + + # Only supported by LVM for now + die "volume underlay is not supported for storage type '$scfg->{type}'\n"; +} + +sub volume_underlay_resize { + my ($class, $scfg, $storeid, $volname, $backing_snap) = @_; + + # Only supported by LVM for now + die "volume underlay is not supported for storage type '$scfg->{type}'\n"; +} + sub volume_snapshot { my ($class, $scfg, $storeid, $volname, $snap) = @_;