From: Fiona Ebner <f.ebner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH qemu-server 06/15] introduce QemuImage module
Date: Mon, 23 Jun 2025 17:44:16 +0200 [thread overview]
Message-ID: <20250623154433.449277-7-f.ebner@proxmox.com> (raw)
In-Reply-To: <20250623154433.449277-1-f.ebner@proxmox.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 122 ++----------------------
src/PVE/QemuServer/ImportDisk.pm | 6 +-
src/PVE/QemuServer/Makefile | 1 +
src/PVE/QemuServer/QemuImage.pm | 123 +++++++++++++++++++++++++
src/test/run_qemu_img_convert_tests.pl | 19 ++--
5 files changed, 148 insertions(+), 123 deletions(-)
create mode 100644 src/PVE/QemuServer/QemuImage.pm
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index b67fe832..63b4d469 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -72,6 +72,7 @@ use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
use PVE::QemuServer::Monitor qw(mon_cmd);
use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
+use PVE::QemuServer::QemuImage;
use PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);
use PVE::QemuServer::RNG qw(parse_rng print_rng_device_commandline print_rng_object_commandline);
use PVE::QemuServer::StateFile;
@@ -7684,7 +7685,12 @@ sub restore_external_archive {
'is-zero-initialized' => $sparseinit,
'source-path-format' => $source_format,
};
- qemu_img_convert($source_path, $d->{volid}, $d->{size}, $convert_opts);
+ PVE::QemuServer::QemuImage::convert(
+ $source_path,
+ $d->{volid},
+ $d->{size},
+ $convert_opts,
+ );
};
my $err = $@;
eval { $backup_provider->restore_vm_volume_cleanup($volname, $d->{devname}, {}); };
@@ -8336,116 +8342,6 @@ sub template_create : prototype($$;$) {
);
}
-sub convert_iscsi_path {
- my ($path) = @_;
-
- if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {
- my $portal = $1;
- my $target = $2;
- my $lun = $3;
-
- my $initiator_name = get_iscsi_initiator_name();
-
- return "file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,"
- . "file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw";
- }
-
- die "cannot convert iscsi path '$path', unknown format\n";
-}
-
-# The possible options are:
-# bwlimit - The bandwidth limit in KiB/s.
-# is-zero-initialized - If the destination image is zero-initialized.
-# snapname - Use this snapshot of the source image.
-# source-path-format - Indicate the format of the source when the source is a path. For PVE-managed
-# volumes, the format from the storage layer is always used.
-sub qemu_img_convert {
- my ($src_volid, $dst_volid, $size, $opts) = @_;
-
- my ($bwlimit, $snapname) = $opts->@{qw(bwlimit snapname)};
-
- my $storecfg = PVE::Storage::config();
- my ($src_storeid) = PVE::Storage::parse_volume_id($src_volid, 1);
- my ($dst_storeid) = PVE::Storage::parse_volume_id($dst_volid, 1);
-
- die "destination '$dst_volid' is not a valid volid form qemu-img convert\n" if !$dst_storeid;
-
- my $cachemode;
- my $src_path;
- my $src_is_iscsi = 0;
- my $src_format;
-
- if ($src_storeid) {
- PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
- my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
- $src_format = checked_volume_format($storecfg, $src_volid);
- $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
- $src_is_iscsi = ($src_path =~ m|^iscsi://|);
- $cachemode = 'none' if $src_scfg->{type} eq 'zfspool';
- } elsif (-f $src_volid || -b $src_volid) {
- $src_path = $src_volid;
- if ($opts->{'source-path-format'}) {
- $src_format = $opts->{'source-path-format'};
- } elsif ($src_path =~ m/\.($PVE::QemuServer::Drive::QEMU_FORMAT_RE)$/) {
- $src_format = $1;
- }
- }
-
- die "source '$src_volid' is not a valid volid nor path for qemu-img convert\n" if !$src_path;
-
- my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
- my $dst_format = checked_volume_format($storecfg, $dst_volid);
- my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
- my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
-
- my $cmd = [];
- push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
- push @$cmd, '-l', "snapshot.name=$snapname"
- if $snapname && $src_format && $src_format eq "qcow2";
- push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
- push @$cmd, '-T', $cachemode if defined($cachemode);
- push @$cmd, '-r', "${bwlimit}K" if defined($bwlimit);
-
- if ($src_is_iscsi) {
- push @$cmd, '--image-opts';
- $src_path = convert_iscsi_path($src_path);
- } elsif ($src_format) {
- push @$cmd, '-f', $src_format;
- }
-
- if ($dst_is_iscsi) {
- push @$cmd, '--target-image-opts';
- $dst_path = convert_iscsi_path($dst_path);
- } else {
- push @$cmd, '-O', $dst_format;
- }
-
- push @$cmd, $src_path;
-
- if (!$dst_is_iscsi && $opts->{'is-zero-initialized'}) {
- push @$cmd, "zeroinit:$dst_path";
- } else {
- push @$cmd, $dst_path;
- }
-
- my $parser = sub {
- my $line = shift;
- if ($line =~ m/\((\S+)\/100\%\)/) {
- my $percent = $1;
- my $transferred = int($size * $percent / 100);
- my $total_h = render_bytes($size, 1);
- my $transferred_h = render_bytes($transferred, 1);
-
- print "transferred $transferred_h of $total_h ($percent%)\n";
- }
-
- };
-
- eval { run_command($cmd, timeout => undef, outfunc => $parser); };
- my $err = $@;
- die "copy failed: $err" if $err;
-}
-
sub qemu_drive_mirror {
my (
$vmid,
@@ -8913,7 +8809,7 @@ sub clone_disk {
'is-zero-initialized' => $sparseinit,
snapname => $snapname,
};
- qemu_img_convert($drive->{file}, $newvolid, $size, $opts);
+ PVE::QemuServer::QemuImage::convert($drive->{file}, $newvolid, $size, $opts);
}
}
}
@@ -8998,7 +8894,7 @@ sub create_efidisk($$$$$$$$) {
my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
PVE::Storage::activate_volumes($storecfg, [$volid]);
- qemu_img_convert($ovmf_vars, $volid, $vars_size_b);
+ PVE::QemuServer::QemuImage::convert($ovmf_vars, $volid, $vars_size_b);
my $size = PVE::Storage::volume_size_info($storecfg, $volid, 3);
return ($volid, $size / 1024);
diff --git a/src/PVE/QemuServer/ImportDisk.pm b/src/PVE/QemuServer/ImportDisk.pm
index 8ecd5521..01289fc5 100755
--- a/src/PVE/QemuServer/ImportDisk.pm
+++ b/src/PVE/QemuServer/ImportDisk.pm
@@ -4,9 +4,11 @@ use strict;
use warnings;
use PVE::Storage;
-use PVE::QemuServer;
use PVE::Tools qw(run_command extract_param);
+use PVE::QemuServer;
+use PVE::QemuServer::QemuImage;
+
# imports an external disk image to an existing VM
# and creates by default a drive entry unused[n] pointing to the created volume
# $params->{drive_name} may be used to specify ide0, scsi1, etc ...
@@ -82,7 +84,7 @@ sub do_import {
local $SIG{PIPE} = sub { die "interrupted by signal $!\n"; };
PVE::Storage::activate_volumes($storecfg, [$dst_volid]);
- PVE::QemuServer::qemu_img_convert(
+ PVE::QemuImage::convert(
$src_path,
$dst_volid,
$src_size,
diff --git a/src/PVE/QemuServer/Makefile b/src/PVE/QemuServer/Makefile
index 7d3830de..a34ec83b 100644
--- a/src/PVE/QemuServer/Makefile
+++ b/src/PVE/QemuServer/Makefile
@@ -15,6 +15,7 @@ SOURCES=Agent.pm \
MetaInfo.pm \
Monitor.pm \
PCI.pm \
+ QemuImage.pm \
QMPHelpers.pm \
RNG.pm \
StateFile.pm \
diff --git a/src/PVE/QemuServer/QemuImage.pm b/src/PVE/QemuServer/QemuImage.pm
new file mode 100644
index 00000000..38f7d52b
--- /dev/null
+++ b/src/PVE/QemuServer/QemuImage.pm
@@ -0,0 +1,123 @@
+package PVE::QemuServer::QemuImage;
+
+use strict;
+use warnings;
+
+use PVE::Format qw(render_bytes);
+use PVE::Storage;
+use PVE::Tools;
+
+use PVE::QemuServer::Drive qw(checked_volume_format);
+use PVE::QemuServer::Helpers;
+
+sub convert_iscsi_path {
+ my ($path) = @_;
+
+ if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {
+ my $portal = $1;
+ my $target = $2;
+ my $lun = $3;
+
+ my $initiator_name = PVE::QemuServer::Helpers::get_iscsi_initiator_name();
+
+ return "file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,"
+ . "file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw";
+ }
+
+ die "cannot convert iscsi path '$path', unknown format\n";
+}
+
+# The possible options are:
+# bwlimit - The bandwidth limit in KiB/s.
+# is-zero-initialized - If the destination image is zero-initialized.
+# snapname - Use this snapshot of the source image.
+# source-path-format - Indicate the format of the source when the source is a path. For PVE-managed
+# volumes, the format from the storage layer is always used.
+sub convert {
+ my ($src_volid, $dst_volid, $size, $opts) = @_;
+
+ my ($bwlimit, $snapname) = $opts->@{qw(bwlimit snapname)};
+
+ my $storecfg = PVE::Storage::config();
+ my ($src_storeid) = PVE::Storage::parse_volume_id($src_volid, 1);
+ my ($dst_storeid) = PVE::Storage::parse_volume_id($dst_volid, 1);
+
+ die "destination '$dst_volid' is not a valid volid form qemu-img convert\n" if !$dst_storeid;
+
+ my $cachemode;
+ my $src_path;
+ my $src_is_iscsi = 0;
+ my $src_format;
+
+ if ($src_storeid) {
+ PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
+ my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
+ $src_format = checked_volume_format($storecfg, $src_volid);
+ $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
+ $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+ $cachemode = 'none' if $src_scfg->{type} eq 'zfspool';
+ } elsif (-f $src_volid || -b $src_volid) {
+ $src_path = $src_volid;
+ if ($opts->{'source-path-format'}) {
+ $src_format = $opts->{'source-path-format'};
+ } elsif ($src_path =~ m/\.($PVE::QemuServer::Drive::QEMU_FORMAT_RE)$/) {
+ $src_format = $1;
+ }
+ }
+
+ die "source '$src_volid' is not a valid volid nor path for qemu-img convert\n" if !$src_path;
+
+ my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+ my $dst_format = checked_volume_format($storecfg, $dst_volid);
+ my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+ my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+
+ my $cmd = [];
+ push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
+ push @$cmd, '-l', "snapshot.name=$snapname"
+ if $snapname && $src_format && $src_format eq "qcow2";
+ push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
+ push @$cmd, '-T', $cachemode if defined($cachemode);
+ push @$cmd, '-r', "${bwlimit}K" if defined($bwlimit);
+
+ if ($src_is_iscsi) {
+ push @$cmd, '--image-opts';
+ $src_path = convert_iscsi_path($src_path);
+ } elsif ($src_format) {
+ push @$cmd, '-f', $src_format;
+ }
+
+ if ($dst_is_iscsi) {
+ push @$cmd, '--target-image-opts';
+ $dst_path = convert_iscsi_path($dst_path);
+ } else {
+ push @$cmd, '-O', $dst_format;
+ }
+
+ push @$cmd, $src_path;
+
+ if (!$dst_is_iscsi && $opts->{'is-zero-initialized'}) {
+ push @$cmd, "zeroinit:$dst_path";
+ } else {
+ push @$cmd, $dst_path;
+ }
+
+ my $parser = sub {
+ my $line = shift;
+ if ($line =~ m/\((\S+)\/100\%\)/) {
+ my $percent = $1;
+ my $transferred = int($size * $percent / 100);
+ my $total_h = render_bytes($size, 1);
+ my $transferred_h = render_bytes($transferred, 1);
+
+ print "transferred $transferred_h of $total_h ($percent%)\n";
+ }
+
+ };
+
+ eval { PVE::Tools::run_command($cmd, timeout => undef, outfunc => $parser); };
+ my $err = $@;
+ die "copy failed: $err" if $err;
+}
+
+1;
diff --git a/src/test/run_qemu_img_convert_tests.pl b/src/test/run_qemu_img_convert_tests.pl
index 86eb53be..b5a457c3 100755
--- a/src/test/run_qemu_img_convert_tests.pl
+++ b/src/test/run_qemu_img_convert_tests.pl
@@ -8,7 +8,7 @@ use lib qw(..);
use Test::More;
use Test::MockModule;
-use PVE::QemuServer;
+use PVE::QemuServer::QemuImage;
my $vmid = 8006;
my $storage_config = {
@@ -498,21 +498,24 @@ $zfsplugin_module->mock(
},
);
-# we use the exported run_command so we have to mock it there
-my $qemu_server_module = Test::MockModule->new("PVE::QemuServer");
-$qemu_server_module->mock(
- run_command => sub {
- $command = shift;
- },
+my $qemu_server_helpers_module = Test::MockModule->new("PVE::QemuServer::Helpers");
+$qemu_server_helpers_module->mock(
get_iscsi_initiator_name => sub {
return "foobar";
},
);
+my $tools_module = Test::MockModule->new("PVE::Tools");
+$tools_module->mock(
+ run_command => sub {
+ $command = shift;
+ },
+);
+
foreach my $test (@$tests) {
my $name = $test->{name};
my $expected = $test->{expected};
- eval { PVE::QemuServer::qemu_img_convert(@{ $test->{parameters} }) };
+ eval { PVE::QemuServer::QemuImage::convert(@{ $test->{parameters} }) };
if (my $err = $@) {
is($err, $expected, $name);
} elsif (defined($command)) {
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-06-23 15:45 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-23 15:44 [pve-devel] [PATCH-SERIES qemu-server 00/15] preparation for blockdev, part two Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 01/15] fix #5985: qmp client: increase timeout for {device, netdev, object}_{add, del} commands Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 02/15] qmp client: add default timeouts for more blockdev commands Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 03/15] helpers: add missing includes Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 04/15] helpers: fix perlcritic warning about variables named $a and $b Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 05/15] move helper for iscsi initiator name to helpers module and improve name Fiona Ebner
2025-06-24 9:48 ` Fabian Grünbichler
2025-06-24 10:05 ` Fiona Ebner
2025-06-24 10:10 ` Fabian Grünbichler
2025-06-23 15:44 ` Fiona Ebner [this message]
2025-06-25 12:54 ` [pve-devel] [PATCH qemu-server 06/15] introduce QemuImage module Daniel Kral
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 07/15] introduce OVMF module Fiona Ebner
2025-06-24 10:23 ` Fabian Grünbichler
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 08/15] blockdev: re-use cache setting from child node Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 09/15] blockdev: add workaround for issue #3229 Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 10/15] blockdev: add support for 'size' option Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 11/15] ovmf: add support for using blockdev Fiona Ebner
2025-06-24 8:38 ` Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [PATCH qemu-server 12/15] cfg2cmd: ovmf: support print_ovmf_commandline() returning machine flags Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [RFC qemu-server 13/15] print drive device: don't reference any drive for 'none' starting with machine version 10.0 Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [RFC qemu-server 14/15] blockdev: add support for NBD paths Fiona Ebner
2025-06-23 15:44 ` [pve-devel] [RFC qemu-server 15/15] command line: switch to blockdev starting with machine version 10.0 Fiona Ebner
2025-06-24 13:53 ` DERUMIER, Alexandre via pve-devel
2025-06-24 14:34 ` Fiona Ebner
2025-06-24 14:41 ` DERUMIER, Alexandre via pve-devel
2025-06-25 11:31 ` DERUMIER, Alexandre via pve-devel
[not found] ` <f3d01b2976480800cfa294cf888534aebadec067.camel@groupe-cyllene.com>
2025-06-25 15:42 ` Fiona Ebner
2025-06-24 9:40 ` [pve-devel] [PATCH-SERIES qemu-server 00/15] preparation for blockdev, part two DERUMIER, Alexandre via pve-devel
2025-06-24 9:59 ` Fiona Ebner
2025-06-24 11:25 ` DERUMIER, Alexandre via pve-devel
2025-06-24 11:44 ` [pve-devel] partially-applied: " Fabian Grünbichler
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250623154433.449277-7-f.ebner@proxmox.com \
--to=f.ebner@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.