From 1d1e4aa3a7192314fe595a45e5b47e223580b5b5 Mon Sep 17 00:00:00 2001 From: Tiago Sousa Date: Fri, 10 Oct 2025 18:02:20 +0100 Subject: [PATCH 1/4] qmeventd: add block write threshold event handling --- src/qmeventd/qmeventd.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/qmeventd/qmeventd.c b/src/qmeventd/qmeventd.c index 1d9eb74a..7bae13f9 100644 --- a/src/qmeventd/qmeventd.c +++ b/src/qmeventd/qmeventd.c @@ -43,7 +43,7 @@ #define DEFAULT_KILL_TIMEOUT 60 -static int verbose = 0; +static int verbose = 1; static int kill_timeout = DEFAULT_KILL_TIMEOUT; static int epoll_fd = 0; static const char *progname; @@ -209,6 +209,25 @@ void handle_qmp_event(struct Client *client, struct json_object *obj) { // check if a backup is running and kill QEMU process if not terminate_check(client); + } else if (!strcmp(json_object_get_string(event), "BLOCK_WRITE_THRESHOLD")) { + struct json_object *data; + struct json_object *nodename; + if (json_object_object_get_ex(obj, "data", &data) && + json_object_object_get_ex(data, "node-name", &nodename)) { + + // needs concurrency control + char extend_queue_path[] = "/etc/pve/extend-queue"; + FILE *p_extend_queue = fopen(extend_queue_path, "a"); + if (p_extend_queue == NULL) { + VERBOSE_PRINT( + "%s: Couldn't open extend queue file %s", client->qemu.vmid, extend_queue_path + ); + } else { + const char *nodename_string = json_object_get_string(nodename); + fprintf(p_extend_queue, "%s: %s\n", client->qemu.vmid, nodename_string); + } + fclose(p_extend_queue); + } } } From 88d7de07207f050035c83e285aac6a61423118bf Mon Sep 17 00:00:00 2001 From: Tiago Sousa Date: Sat, 2 Aug 2025 16:58:53 +0100 Subject: [PATCH 2/4] blockdev: add set write threshold blockdev: set write threshold --- src/PVE/QemuServer.pm | 20 +++++++++++++ src/PVE/QemuServer/Blockdev.pm | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index e30b27cb..994b7ac1 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -5822,6 +5822,26 @@ sub vm_start_nolock { warn $@ if $@; } + # set write threshold for LVM thin provisioning disks + PVE::QemuConfig->foreach_volume( + $conf, + sub { + my ($ds, $drive) = @_; + return if PVE::QemuServer::drive_is_cdrom($drive, 1); + if ($drive->{file} ne 'none') { + my $extra_blockdev_options = {}; + # $extra_blockdev_options->{'live-restore'} = $live_restore if $live_restore; + # extra protection for templates, but SATA and IDE don't support it.. + $extra_blockdev_options->{'read-only'} = 1 + if drive_is_read_only($conf, $drive); + + PVE::QemuServer::Blockdev::set_write_threshold( + $storecfg, $vmid, $drive, $extra_blockdev_options, + ); + } + }, + ); + #start nbd server for storage migration if (my $nbd = $migrate_opts->{nbd}) { diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm index d0d7e684..d17335a1 100644 --- a/src/PVE/QemuServer/Blockdev.pm +++ b/src/PVE/QemuServer/Blockdev.pm @@ -820,6 +820,59 @@ sub set_io_throttle { } } +sub block_set_write_threshold { + my ($vmid, $nodename, $threshold) = @_; + + print "set threshold $nodename $threshold\n"; + + PVE::QemuServer::mon_cmd( + $vmid, + "block-set-write-threshold", + 'node-name' => $nodename, + 'write-threshold' => int($threshold), + ); +} + +sub compute_write_threshold { + my ($storecfg, $volid) = @_; + + my $lv_size = PVE::Storage::volume_size_info($storecfg, $volid, 5); + + # FIX: change these vars to config inputs + my $chunksize = 1024 * 1024 * 1024; # 1 GiB + my $alert_chunk_percentage = 0.5; # alert when percetage of chunk used + + my $write_threshold = $lv_size - $chunksize * (1 - $alert_chunk_percentage); + + return $write_threshold; +} + +sub set_write_threshold { + my ($storecfg, $vmid, $drive, $options) = @_; + + my $volid = $drive->{'file'}; + my ($storeid) = PVE::Storage::parse_volume_id($volid); + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + my $support_qemu_snapshots = PVE::Storage::volume_qemu_snapshot_method($storecfg, $volid); + + # set write threshold is only supported for lvm storage using + # qcow2+external snapshots + return if $scfg->{type} ne 'lvm' || $support_qemu_snapshots ne 'mixed'; + + print "setting threshold for $volid from $storeid\n"; + + my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid); + my $parentid = $snapshots->{'current'}->{parent}; + # for now only set write_threshold for volumes that have snapshots + if ($parentid) { + my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive); + my $nodename = get_node_name('file', $drive_id, $volid, $options); + my $write_threshold = compute_write_threshold($storecfg, $volid); + + block_set_write_threshold($vmid, $nodename, $write_threshold); + } +} + sub blockdev_external_snapshot { my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_; @@ -868,6 +921,8 @@ sub blockdev_external_snapshot { node => $snap_fmt_blockdev->{'node-name'}, overlay => $new_fmt_blockdev->{'node-name'}, ); + + set_write_threshold($storecfg, $vmid, $drive); } sub blockdev_delete { From 7fe84f290210f0961df315341c32731352d04c11 Mon Sep 17 00:00:00 2001 From: Tiago Sousa Date: Sat, 13 Sep 2025 18:54:06 +0100 Subject: [PATCH 3/4] blockdev: add query-blockstats qmp command --- src/PVE/QemuServer/Blockdev.pm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm index d17335a1..7fef446b 100644 --- a/src/PVE/QemuServer/Blockdev.pm +++ b/src/PVE/QemuServer/Blockdev.pm @@ -111,6 +111,21 @@ sub get_block_info { return $block_info; } +sub get_block_stats { + my ($vmid) = @_; + + my $block_stats = {}; + + my $qmp_block_stats = mon_cmd($vmid, "query-blockstats"); + for my $info ($qmp_block_stats->@*) { + my $qdev_id = $info->{qdev} or next; + my $drive_id = qdev_id_to_drive_id($qdev_id); + $block_stats->{$drive_id} = $info; + } + + return $block_stats; +} + my sub get_node_name { my ($type, $drive_id, $volid, $options) = @_; From be6264ebbf1e119bfec864717b8f53ce7b7c0976 Mon Sep 17 00:00:00 2001 From: Tiago Sousa Date: Sat, 20 Sep 2025 18:49:23 +0100 Subject: [PATCH 4/4] first functional version of extend lv --- src/PVE/QemuServer.pm | 22 +++++++------- src/PVE/QemuServer/Blockdev.pm | 52 ++++++++++++++++++++-------------- src/PVE/QemuServer/Drive.pm | 7 +++++ 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 994b7ac1..df849ca1 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -5828,16 +5828,18 @@ sub vm_start_nolock { sub { my ($ds, $drive) = @_; return if PVE::QemuServer::drive_is_cdrom($drive, 1); - if ($drive->{file} ne 'none') { - my $extra_blockdev_options = {}; - # $extra_blockdev_options->{'live-restore'} = $live_restore if $live_restore; - # extra protection for templates, but SATA and IDE don't support it.. - $extra_blockdev_options->{'read-only'} = 1 - if drive_is_read_only($conf, $drive); - - PVE::QemuServer::Blockdev::set_write_threshold( - $storecfg, $vmid, $drive, $extra_blockdev_options, - ); + my $volid = $drive->{file}; + if ( $volid ne 'none') { + my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive); + my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid); + my $parentid = $snapshots->{'current'}->{parent}; + # for now only set write_threshold for volumes that have snapshots + # FIX: Change to only thin drives + if ($parentid) { + PVE::QemuServer::Blockdev::set_write_threshold( + $storecfg, $vmid, $drive_id, $volid + ); + } } }, ); diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm index 7fef446b..3d78dcde 100644 --- a/src/PVE/QemuServer/Blockdev.pm +++ b/src/PVE/QemuServer/Blockdev.pm @@ -715,6 +715,24 @@ sub resize { ); } +sub underlay_resize { + my ($storecfg, $vmid, $drive_id, $volid) = @_; + + my $running = PVE::QemuServer::Helpers::vm_running_locally($vmid); + + # get backing_snap + my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid); + my $backing_snap = $snapshots->{current}->{parent}; + my $size = PVE::Storage::volume_underlay_resize($storecfg, $volid, $backing_snap); + + return if !$running; + my $block_info = get_block_info($vmid); + my $inserted = $block_info->{$drive_id}->{inserted} + or die "no block node inserted for drive '$drive_id'\n"; + + set_write_threshold($storecfg, $vmid, $drive_id, $volid); +} + my sub blockdev_change_medium { my ($storecfg, $vmid, $qdev_id, $drive) = @_; @@ -849,23 +867,18 @@ sub block_set_write_threshold { } sub compute_write_threshold { - my ($storecfg, $volid) = @_; + my ($storecfg, $scfg, $volid) = @_; - my $lv_size = PVE::Storage::volume_size_info($storecfg, $volid, 5); + my $lv_size = PVE::Storage::volume_underlay_size_info($storecfg, $volid, 5); - # FIX: change these vars to config inputs - my $chunksize = 1024 * 1024 * 1024; # 1 GiB - my $alert_chunk_percentage = 0.5; # alert when percetage of chunk used - - my $write_threshold = $lv_size - $chunksize * (1 - $alert_chunk_percentage); + my $write_threshold = $lv_size - $scfg->{chunksize} * (1 - $scfg->{'chunk-percentage'}); return $write_threshold; } sub set_write_threshold { - my ($storecfg, $vmid, $drive, $options) = @_; + my ($storecfg, $vmid, $drive_id, $volid, $options) = @_; - my $volid = $drive->{'file'}; my ($storeid) = PVE::Storage::parse_volume_id($volid); my $scfg = PVE::Storage::storage_config($storecfg, $storeid); my $support_qemu_snapshots = PVE::Storage::volume_qemu_snapshot_method($storecfg, $volid); @@ -873,19 +886,14 @@ sub set_write_threshold { # set write threshold is only supported for lvm storage using # qcow2+external snapshots return if $scfg->{type} ne 'lvm' || $support_qemu_snapshots ne 'mixed'; + # return if drive is not set as thin + # return if !$drive->{thin}; + + my $nodename = get_node_name('file', $drive_id, $volid, $options); + my $write_threshold = compute_write_threshold($storecfg, $scfg, $volid); print "setting threshold for $volid from $storeid\n"; - - my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid); - my $parentid = $snapshots->{'current'}->{parent}; - # for now only set write_threshold for volumes that have snapshots - if ($parentid) { - my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive); - my $nodename = get_node_name('file', $drive_id, $volid, $options); - my $write_threshold = compute_write_threshold($storecfg, $volid); - - block_set_write_threshold($vmid, $nodename, $write_threshold); - } + block_set_write_threshold($vmid, $nodename, $write_threshold); } sub blockdev_external_snapshot { @@ -937,7 +945,9 @@ sub blockdev_external_snapshot { overlay => $new_fmt_blockdev->{'node-name'}, ); - set_write_threshold($storecfg, $vmid, $drive); + # FIX: only if thin + my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive); + set_write_threshold($storecfg, $vmid, $drive_id, $volid); } sub blockdev_delete { diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm index 9dc4e674..8d5ddebf 100644 --- a/src/PVE/QemuServer/Drive.pm +++ b/src/PVE/QemuServer/Drive.pm @@ -254,6 +254,13 @@ my %drivedesc_base = ( optional => 1, default => 0, }, + thin => { + type => 'boolean', + description => + 'Controls whether a drive should be thin provisioned', + optional => 1, + default => 0, + }, ); my %iothread_fmt = (