Signed-off-by: Wolfgang Bumiller <w.bumil...@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@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@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@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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel