[pve-devel] [PATCH qemu-server] restore: support i/o rate limiting

Wolfgang Bumiller w.bumiller at proxmox.com
Tue Feb 6 14:51:53 CET 2018


Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
This required some refactoring of and currently only handles VMA
archives (wanted to get this out first).
Note that this means we need to extract the config via `vma config ...`
first which of course means this cannot apply to archives coming from a
pipe (but there we're root at pam anyway so that doesn't make a
difference.)
Alternatively a 'ratelimit' command for the FIFO we're talking to vma
with could be added and the bandwidth limit figured out later in the
process. This would actually be a lot less work, but the ratelimit
implementation would be inside vma, iow. rolled out with the
pve-qemu-kvm package.

 PVE/API2/Qemu.pm     |  11 +++-
 PVE/CLI/qmrestore.pm |   6 ++
 PVE/QemuServer.pm    | 156 +++++++++++++++++++++++++++++++++------------------
 3 files changed, 118 insertions(+), 55 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index b277a26..acb4db7 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 i/o bandwidth limit (in KiB/s).",
+		    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..9ec0051 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 i/o bandwidth limit (in KiB/s).",
+		optional => 1,
+		type => 'number',
+		minimum => '0',
+	    }
 	},
     },
     returns => { 
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 6692888..2738f11 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -5530,25 +5530,107 @@ sub rescan {
 sub restore_vma_archive {
     my ($archive, $vmid, $user, $opts, $comp) = @_;
 
-    my $input = $archive eq '-' ? "<&STDIN" : undef;
+    my $tmpdir = "/var/tmp/vzdumptmp$$";
+    rmtree $tmpdir;
+
+    mkdir '/run/pve';
+    my $mapfifo = "/run/pve/vzdumptmp$$.fifo";
+    POSIX::mkfifo($mapfifo, 0600);
+    my $fifofh;
+
+    my $openfifo = sub {
+	open($fifofh, '>', $mapfifo) || die $!;
+    };
+
     my $readfrom = $archive;
 
-    my $uncomp = '';
+    my $commands = [];
+    my $cmdstring = ''; # TODO: extend Tools::cmd2string to support arrays
     if ($comp) {
 	$readfrom = '-';
 	my $qarchive = PVE::Tools::shellquote($archive);
+	my $cmd;
 	if ($comp eq 'gzip') {
-	    $uncomp = "zcat $qarchive|";
+	    $cmd = ['zcat', $qarchive];
 	} elsif ($comp eq 'lzop') {
-	    $uncomp = "lzop -d -c $qarchive|";
+	    $cmd = ['lzop', '-d', '-c', $qarchive];
 	} else {
 	    die "unknown compression method '$comp'\n";
 	}
+	push @$commands, $cmd;
+	$cmdstring .= PVE::Tools::cmd2string($cmd).' |';
+    }
+
+    my $rpcenv = PVE::RPCEnvironment::get();
+    my $cfg = PVE::Storage::config();
+    my $devinfo = {};
+    my $virtdev_hash;
+    my %used_storages;
+
+    my $parse_devinfo = sub {
+	my ($line) = @_;
+	if ($line =~ m/^\#qmdump\#map:(\S+):(\S+):(\S*):(\S*):$/) {
+	    my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4);
+	    if (defined($opts->{storage})) {
+		$storeid = $opts->{storage} || 'local';
+	    } elsif (!$storeid) {
+		$storeid = 'local';
+	    }
+	    $format = 'raw' if !$format;
+	    $devinfo->{$devname}->{devname} = $devname;
+	    $devinfo->{$devname}->{virtdev} = $virtdev;
+	    $devinfo->{$devname}->{format} = $format;
+	    $devinfo->{$devname}->{storeid} = $storeid;
+
+	    # check permission on storage
+	    my $pool = $opts->{pool}; # todo: do we need that?
+	    if ($user ne 'root at pam') {
+		$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
+	    }
+
+	    $used_storages{$storeid} = 1;
+
+	    $virtdev_hash->{$virtdev} = $devinfo->{$devname};
+	}
+    };
+
+    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);
+	    $used_storages{$sid} = 1;
+	}
 
+	$virtdev_hash = {};
+	run_command([@$commands, [qw(vma config -c qemu-server.conf), $readfrom]],
+	    outfunc => $parse_devinfo);
     }
 
-    my $tmpdir = "/var/tmp/vzdumptmp$$";
-    rmtree $tmpdir;
+    my $bwlimit = $opts->{bwlimit};
+
+    $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
+    if ($bwlimit) {
+	$bwlimit *= 1024;
+	print STDERR "ratelimit: $bwlimit\n";
+
+	my $cstream = ['cstream', '-t', $bwlimit];
+	if ($readfrom ne '-') {
+	    # it doesn't like `-- -`
+	    push @$cstream, '--', $readfrom;
+	}
+	push @$commands, $cstream;
+	$readfrom = '-';
+	$cmdstring .= PVE::Tools::cmd2string($cstream).' |';
+    }
+
+    my $vmacmd = ['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir];
+    push @$commands, $vmacmd;
+    $cmdstring .= PVE::Tools::cmd2string($vmacmd);
 
     # disable interrupts (always do cleanups)
     local $SIG{INT} =
@@ -5556,23 +5638,9 @@ sub restore_vma_archive {
 	local $SIG{QUIT} =
 	local $SIG{HUP} = sub { warn "got interrupt - ignored\n"; };
 
-    my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
-    POSIX::mkfifo($mapfifo, 0600);
-    my $fifofh;
-
-    my $openfifo = sub {
-	open($fifofh, '>', $mapfifo) || die $!;
-    };
-
-    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
-
     my $oldtimeout;
     my $timeout = 5;
 
-    my $devinfo = {};
-
-    my $rpcenv = PVE::RPCEnvironment::get();
-
     my $conffile = PVE::QemuConfig->config_file($vmid);
     my $tmpfn = "$conffile.$$.tmp";
 
@@ -5581,8 +5649,6 @@ sub restore_vma_archive {
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
 
     my $print_devmap = sub {
-	my $virtdev_hash = {};
-
 	my $cfgfn = "$tmpdir/qemu-server.conf";
 
 	# we can read the config - that is already extracted
@@ -5596,39 +5662,23 @@ sub restore_vma_archive {
 	    PVE::Tools::file_copy($fwcfgfn, "${pve_firewall_dir}/$vmid.fw");
 	}
 
-	while (defined(my $line = <$fh>)) {
-	    if ($line =~ m/^\#qmdump\#map:(\S+):(\S+):(\S*):(\S*):$/) {
-		my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4);
-		die "archive does not contain data for drive '$virtdev'\n"
-		    if !$devinfo->{$devname};
-		if (defined($opts->{storage})) {
-		    $storeid = $opts->{storage} || 'local';
-		} elsif (!$storeid) {
-		    $storeid = 'local';
-		}
-		$format = 'raw' if !$format;
-		$devinfo->{$devname}->{devname} = $devname;
-		$devinfo->{$devname}->{virtdev} = $virtdev;
-		$devinfo->{$devname}->{format} = $format;
-		$devinfo->{$devname}->{storeid} = $storeid;
-
-		# check permission on storage
-		my $pool = $opts->{pool}; # todo: do we need that?
-		if ($user ne 'root at pam') {
-		    $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
-		}
-
-		$virtdev_hash->{$virtdev} = $devinfo->{$devname};
+	if (!defined($virtdev_hash)) {
+	    # When reading from stdin we get this info here
+	    $virtdev_hash = {};
+	    while (defined(my $line = <$fh>)) {
+		$parse_devinfo->($line);
 	    }
+	    $fh->seek(0, 0) || die "seek failed - $!\n";
 	}
 
 	foreach my $devname (keys %$devinfo) {
+	    my $dev = $devinfo->{$devname};
 	    die "found no device mapping information for device '$devname'\n"
-		if !$devinfo->{$devname}->{virtdev};
+		if !$dev->{virtdev};
+	    die "archive does not contain data for drive '$dev->{virtdev}'\n"
+		if !exists $dev->{dev_id};
 	}
 
-	my $cfg = PVE::Storage::config();
-
 	# create empty/temp config
 	if ($oldconf) {
 	    PVE::Tools::file_set_contents($conffile, "memory: 128\n");
@@ -5697,8 +5747,6 @@ sub restore_vma_archive {
 	    $map->{$virtdev} = $volid;
 	}
 
-	$fh->seek(0, 0) || die "seek failed - $!\n";
-
 	my $outfd = new IO::File ($tmpfn, "w") ||
 	    die "unable to write config for VM $vmid\n";
 
@@ -5729,7 +5777,8 @@ sub restore_vma_archive {
 
 	    if ($line =~ m/^DEV:\sdev_id=(\d+)\ssize:\s(\d+)\sdevname:\s(\S+)$/) {
 		my ($dev_id, $size, $devname) = ($1, $2, $3);
-		$devinfo->{$devname} = { size => $size, dev_id => $dev_id };
+		$devinfo->{$devname}->{size} = $size;
+		$devinfo->{$devname}->{dev_id} = $dev_id;
 	    } elsif ($line =~ m/^CTIME: /) {
 		# we correctly received the vma config, so we can disable
 		# the timeout now for disk allocation (set to 10 minutes, so
@@ -5744,8 +5793,8 @@ sub restore_vma_archive {
 	    }
 	};
 
-	print "restore vma archive: $cmd\n";
-	run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+	print "restore vma archive: $cmdstring\n";
+	run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
     };
     my $err = $@;
 
@@ -5757,7 +5806,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