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

Reply via email to