[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