[pve-devel] [PATCH v2 qemu-server] restore: implement rate limiting

Wolfgang Bumiller w.bumiller at proxmox.com
Thu Feb 15 13:41:23 CET 2018


Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
This version avoids the refactoring and double-reading of the VMA file,
thereby supporting reading from a pipe again.
Instead this now uses an extension to vma extract (see the other patch),
and instead of picking the lowest limit altogether, applies limits to
disks separately per-storage by creating a throttling group per storage
id.

 PVE/API2/Qemu.pm     | 11 +++++++-
 PVE/CLI/qmrestore.pm |  6 ++++
 PVE/QemuServer.pm    | 78 ++++++++++++++++++++++++++++++++++++++++------------
 3 files changed, 77 insertions(+), 18 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index b277a26..a63f2c9 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -405,6 +405,12 @@ __PACKAGE__->register_method({
 		    type => 'string', format => 'pve-poolid',
 		    description => "Add the VM to the specified pool.",
 		},
+		bwlimit => {
+		    description => "Override io bandwidth.",
+		    optional => 1,
+		    type => 'number',
+		    minimum => '0',
+		}
 	    }),
     },
     returns => {
@@ -431,6 +437,8 @@ __PACKAGE__->register_method({
 
 	my $pool = extract_param($param, 'pool');
 
+	my $bwlimit = extract_param($param, 'bwlimit');
+
 	my $filename = PVE::QemuConfig->config_file($vmid);
 
 	my $storecfg = PVE::Storage::config();
@@ -513,7 +521,8 @@ __PACKAGE__->register_method({
 		PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
 		    storage => $storage,
 		    pool => $pool,
-		    unique => $unique });
+		    unique => $unique,
+		    bwlimit => $bwlimit, });
 
 		PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
 	    };
diff --git a/PVE/CLI/qmrestore.pm b/PVE/CLI/qmrestore.pm
index 17018d2..7e12b10 100755
--- a/PVE/CLI/qmrestore.pm
+++ b/PVE/CLI/qmrestore.pm
@@ -53,6 +53,12 @@ __PACKAGE__->register_method({
 		type => 'string', format => 'pve-poolid',
 		description => "Add the VM to the specified pool.",
 	    },
+	    bwlimit => {
+		description => "Override io bandwidth.",
+		optional => 1,
+		type => 'number',
+		minimum => '0',
+	    }
 	},
     },
     returns => { 
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 20d6682..1607932 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -5539,21 +5539,52 @@ sub rescan {
 sub restore_vma_archive {
     my ($archive, $vmid, $user, $opts, $comp) = @_;
 
-    my $input = $archive eq '-' ? "<&STDIN" : undef;
     my $readfrom = $archive;
 
-    my $uncomp = '';
-    if ($comp) {
+    my $cfg = PVE::Storage::config();
+    my $commands = [];
+    my $dbg_cmdstring = '';
+    my $bwlimit = $opts->{bwlimit};
+
+    my $add_pipe = sub {
+	my ($cmd, $final) = @_;
+	push @$commands, $cmd;
+	$dbg_cmdstring .= PVE::Tools::cmd2string($cmd);
+	$dbg_cmdstring .= ' | ' if !$final;
 	$readfrom = '-';
-	my $qarchive = PVE::Tools::shellquote($archive);
+    };
+
+    my $input = undef;
+    if ($archive eq '-') {
+	$input = '<&STDIN';
+    } else {
+	# If we use a backup from a PVE defined storage we also consider that
+	# storage's rate limit:
+	my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);
+	if (defined($volid)) {
+	    my ($sid, undef) = PVE::Storage::parse_volume_id($volid);
+	    my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);
+	    if ($readlimit) {
+		print STDERR "applying read rate limit: $readlimit\n";
+		my $cstream = ['cstream', '-t', $readlimit*1024];
+		if ($readfrom ne '-') {
+		    push @$cstream, '--', $readfrom;
+		}
+		$add_pipe->($cstream);
+	    }
+	}
+    }
+
+    if ($comp) {
+	my $cmd;
 	if ($comp eq 'gzip') {
-	    $uncomp = "zcat $qarchive|";
+	    $cmd = ['zcat', $readfrom];
 	} elsif ($comp eq 'lzop') {
-	    $uncomp = "lzop -d -c $qarchive|";
+	    $cmd = ['lzop', '-d', '-c', $readfrom];
 	} else {
 	    die "unknown compression method '$comp'\n";
 	}
-
+	$add_pipe->($cmd);
     }
 
     my $tmpdir = "/var/tmp/vzdumptmp$$";
@@ -5573,7 +5604,7 @@ sub restore_vma_archive {
 	open($fifofh, '>', $mapfifo) || die $!;
     };
 
-    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+    $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
 
     my $oldtimeout;
     my $timeout = 5;
@@ -5589,6 +5620,8 @@ sub restore_vma_archive {
     my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
 
+    my %storage_limits;
+
     my $print_devmap = sub {
 	my $virtdev_hash = {};
 
@@ -5627,17 +5660,23 @@ sub restore_vma_archive {
 		    $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
 		}
 
+		$storage_limits{$storeid} = $bwlimit;
+
 		$virtdev_hash->{$virtdev} = $devinfo->{$devname};
 	    }
 	}
 
+	foreach my $key (keys %storage_limits) {
+	    my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
+	    print STDERR "rate limit for storage $key: $limit KiB/s\n";
+	    $storage_limits{$key} = $limit * 1024;
+	}
+
 	foreach my $devname (keys %$devinfo) {
 	    die "found no device mapping information for device '$devname'\n"
 		if !$devinfo->{$devname}->{virtdev};
 	}
 
-	my $cfg = PVE::Storage::config();
-
 	# create empty/temp config
 	if ($oldconf) {
 	    PVE::Tools::file_set_contents($conffile, "memory: 128\n");
@@ -5680,14 +5719,20 @@ sub restore_vma_archive {
 	foreach my $virtdev (sort keys %$virtdev_hash) {
 	    my $d = $virtdev_hash->{$virtdev};
 	    my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
-	    my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+	    my $storeid = $d->{storeid};
+	    my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+
+	    my $map_opts = '';
+	    if (my $limit = $storage_limits{$storeid}) {
+		$map_opts .= "throttling.bps=$limit:throttling.group=$storeid:";
+	    }
 
 	    # test if requested format is supported
-	    my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+	    my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid);
 	    my $supported = grep { $_ eq $d->{format} } @$validFormats;
 	    $d->{format} = $defFormat if !$supported;
 
-	    my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+	    my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
 						  $d->{format}, undef, $alloc_size);
 	    print STDERR "new volume ID is '$volid'\n";
 	    $d->{volid} = $volid;
@@ -5700,7 +5745,7 @@ sub restore_vma_archive {
 		$write_zeros = 0;
 	    }
 
-	    print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+	    print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
 
 	    print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
 	    $map->{$virtdev} = $volid;
@@ -5753,8 +5798,8 @@ sub restore_vma_archive {
 	    }
 	};
 
-	print "restore vma archive: $cmd\n";
-	run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+	print "restore vma archive: $dbg_cmdstring\n";
+	run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
     };
     my $err = $@;
 
@@ -5766,7 +5811,6 @@ sub restore_vma_archive {
 	push @$vollist, $volid if $volid;
     }
 
-    my $cfg = PVE::Storage::config();
     PVE::Storage::deactivate_volumes($cfg, $vollist);
 
     unlink $mapfifo;
-- 
2.11.0





More information about the pve-devel mailing list