[pve-devel] [PATCH qemu-server 06/15] introduce QemuImage module
Fiona Ebner
f.ebner at proxmox.com
Mon Jun 23 17:44:16 CEST 2025
Signed-off-by: Fiona Ebner <f.ebner at 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
More information about the pve-devel
mailing list