]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/VZDump.pm
Pass what storage ID is being used to vzdump hook scripts
[pve-manager.git] / PVE / VZDump.pm
index 5ae091e4740137d951018450ccfdaddd225d4f1d..0bef3fdf420bf716475d5205e8975964b9a0793d 100644 (file)
@@ -10,6 +10,7 @@ use IO::Select;
 use IPC::Open3;
 use POSIX qw(strftime);
 use File::Path;
+use PVE::RPCEnvironment;
 use PVE::Storage;
 use PVE::Cluster qw(cfs_read_file);
 use PVE::VZDump::OpenVZ;
@@ -91,6 +92,7 @@ sub storage_info {
 
     return {
        dumpdir => PVE::Storage::get_backup_dir($cfg, $storage),
+       maxfiles => $scfg->{maxfiles},
     };
 }
 
@@ -438,6 +440,10 @@ sub new {
 
     my $defaults = read_vzdump_defaults();
 
+    my $maxfiles = $opts->{maxfiles}; # save here, because we overwrite with default
+
+    $opts->{remove} = 1 if !defined($opts->{remove});
+
     foreach my $k (keys %$defaults) {
        if ($k eq 'dumpdir' || $k eq 'storage') {
            $opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir}) &&
@@ -482,20 +488,16 @@ sub new {
        my $pd = $p->new ($self);
 
        push @{$self->{plugins}}, $pd;
-
-       if (!$opts->{dumpdir} && !$opts->{storage} && 
-           ($p eq 'PVE::VZDump::OpenVZ')) {
-           $opts->{dumpdir} = $pd->{dumpdir};
-       }
     }
 
     if (!$opts->{dumpdir} && !$opts->{storage}) {
-       die "no dumpdir/storage specified - use option '--dumpdir' or option '--storage'\n";
+       $opts->{storage} = 'local';
     }
 
     if ($opts->{storage}) {
        my $info = storage_info ($opts->{storage});
        $opts->{dumpdir} = $info->{dumpdir};
+       $maxfiles = $info->{maxfiles} if !defined($maxfiles) && defined($info->{maxfiles});
     } elsif ($opts->{dumpdir}) {
        die "dumpdir '$opts->{dumpdir}' does not exist\n"
            if ! -d $opts->{dumpdir};
@@ -507,6 +509,8 @@ sub new {
        die "tmpdir '$opts->{tmpdir}' does not exist\n";
     }
 
+    $opts->{maxfiles} = $maxfiles if defined($maxfiles);
+
     return $self;
 
 }
@@ -515,22 +519,25 @@ sub get_lvm_mapping {
 
     my $devmapper;
 
-    my $cmd = "lvs --units m --separator ':' --noheadings -o vg_name,lv_name,lv_size";
-    if (my $fd = IO::File->new ("$cmd 2>/dev/null|")) {
-       while (my $line = <$fd>) {
-           if ($line =~ m|^\s*(\S+):(\S+):(\d+(\.\d+))[Mm]$|) {
-               my $vg = $1;
-               my $lv = $2;
-               $devmapper->{"/dev/$vg/$lv"} = [$vg, $lv];
-               my $qlv = $lv;
-               $qlv =~ s/-/--/g;
-               my $qvg = $vg;
-               $qvg =~ s/-/--/g;
-               $devmapper->{"/dev/mapper/$qvg-$qlv"} = [$vg, $lv];
-           }
-       }
-       close ($fd);
-    }
+    my $cmd = ['lvs', '--units', 'm', '--separator', ':', '--noheadings',
+              '-o', 'vg_name,lv_name,lv_size' ];
+
+    my $parser = sub {
+       my $line = shift;
+       if ($line =~ m|^\s*(\S+):(\S+):(\d+(\.\d+))[Mm]$|) {
+           my $vg = $1;
+           my $lv = $2;
+           $devmapper->{"/dev/$vg/$lv"} = [$vg, $lv];
+           my $qlv = $lv;
+           $qlv =~ s/-/--/g;
+           my $qvg = $vg;
+           $qvg =~ s/-/--/g;
+           $devmapper->{"/dev/mapper/$qvg-$qlv"} = [$vg, $lv];
+       }                       
+    };
+
+    eval { PVE::Tools::run_command($cmd, errfunc => sub {}, outfunc => $parser); };
+    warn $@ if $@;
 
     return $devmapper;
 }
@@ -538,24 +545,28 @@ sub get_lvm_mapping {
 sub get_mount_info {
     my ($dir) = @_;
 
-    my $out;
-    if (my $fd = IO::File->new ("df -P -T '$dir' 2>/dev/null|")) {
-       <$fd>; #skip first line
-       $out = <$fd>;
-       close ($fd);
-    }
+    # Note: df 'available' can be negative, and percentage set to '-'
 
-    return undef if !$out;
-   
-    my @res = $out =~ m/^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.*)$/;
+    my $cmd = [ 'df', '-P', '-T', '-B', '1', $dir];
 
-    return undef if scalar (@res) != 7;
+    my $res;
 
-    return {
-       device => $res[0],
-       fstype => $res[1],
-       mountpoint => $res[6]
+    my $parser = sub {
+       my $line = shift;
+       if (my ($fsid, $fstype, undef, $mp) = $line =~
+           m!(\S+.*)\s+(\S+)\s+\d+\s+\-?\d+\s+\d+\s+(\d+%|-)\s+(/.*)$!) {
+           $res = {
+               device => $fsid,
+               fstype => $fstype,
+               mountpoint => $mp,
+           };
+       }
     };
+
+    eval { PVE::Tools::run_command($cmd, errfunc => sub {}, outfunc => $parser); };
+    warn $@ if $@;
+
+    return $res;
 }
 
 sub get_lvm_device {
@@ -635,13 +646,43 @@ sub run_hook_script {
 
     local %ENV;
 
-    foreach my $ek (qw(vmtype dumpdir hostname tarfile logfile)) {
+    foreach my $ek (qw(vmtype dumpdir hostname tarfile logfile storeid)) {
        $ENV{uc($ek)} = $task->{$ek} if $task->{$ek};
     }
 
     run_command ($logfd, $cmd);
 }
 
+sub compressor_info {
+    my ($opt_compress) = @_;
+
+    if (!$opt_compress || $opt_compress eq '0') {
+       return undef;
+    } elsif ($opt_compress eq '1' || $opt_compress eq 'lzo') {
+       return ('lzop', 'lzo');
+    } elsif ($opt_compress eq 'gzip') {
+       return ('gzip', 'gz');
+    } else {
+       die "internal error - unknown compression option '$opt_compress'";
+    }
+}
+
+sub get_backup_file_list {
+    my ($dir, $bkname, $exclude_fn) = @_;
+
+    my $bklist = [];
+    foreach my $fn (<$dir/${bkname}-*>) {
+       next if $exclude_fn && $fn eq $exclude_fn;
+       if ($fn =~ m!/(${bkname}-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|((tar|vma)(\.(gz|lzo))?)))$!) {
+           $fn = "$dir/$1"; # untaint
+           my $t = timelocal ($7, $6, $5, $4, $3 - 1, $2 - 1900);
+           push @$bklist, [$fn, $t];
+       }
+    }
+
+    return $bklist;
+}
 sub exec_backup_task {
     my ($self, $task) = @_;
         
@@ -672,9 +713,21 @@ sub exec_backup_task {
        $lt->year + 1900, $lt->mon + 1, $lt->mday, 
        $lt->hour, $lt->min, $lt->sec;
 
+       my $maxfiles = $opts->{maxfiles};
+
+       if ($maxfiles && !$opts->{remove}) {
+           my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname);
+           die "only $maxfiles backup(s) allowed - please consider to remove old backup files.\n" 
+               if scalar(@$bklist) >= $maxfiles;
+       }
+
        my $logfile = $task->{logfile} = "$opts->{dumpdir}/$basename.log";
 
-       my $ext = $opts->{compress} ? '.tgz' : '.tar';
+       my $ext = $vmtype eq 'qemu' ? '.vma' : '.tar';
+       my ($comp, $comp_ext) = compressor_info($opts->{compress});
+       if ($comp && $comp_ext) {
+           $ext .= ".${comp_ext}";
+       }
 
        if ($opts->{stdout}) {
            $task->{tarfile} = '-';
@@ -711,7 +764,7 @@ sub exec_backup_task {
            die "unable to create log file '$tmplog'";
 
        $task->{dumpdir} = $opts->{dumpdir};
-
+       $task->{storeid} = $opts->{storage};
        $task->{tmplog} = $tmplog;
 
        unlink $logfile;
@@ -801,6 +854,8 @@ sub exec_backup_task {
 
        } elsif ($mode eq 'snapshot') {
 
+           $self->run_hook_script ('backup-start', $task, $logfd);
+
            my $snapshot_count = $task->{snapshot_count} || 0;
 
            $self->run_hook_script ('pre-stop', $task, $logfd);
@@ -835,13 +890,13 @@ sub exec_backup_task {
 
        if ($opts->{stdout}) {
            debugmsg ('info', "sending archive to stdout", $logfd);
-           $plugin->archive($task, $vmid, $task->{tmptar});
+           $plugin->archive($task, $vmid, $task->{tmptar}, $comp);
            $self->run_hook_script ('backup-end', $task, $logfd);
            return;
        }
 
        debugmsg ('info', "creating archive '$task->{tarfile}'", $logfd);
-       $plugin->archive ($task, $vmid, $task->{tmptar});
+       $plugin->archive($task, $vmid, $task->{tmptar}, $comp);
 
        rename ($task->{tmptar}, $task->{tarfile}) ||
            die "unable to rename '$task->{tmptar}' to '$task->{tarfile}'\n";
@@ -853,30 +908,16 @@ sub exec_backup_task {
 
        # purge older backup
 
-       my $maxfiles = $opts->{maxfiles};
+       if ($maxfiles && $opts->{remove}) {
+           my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname, $task->{tarfile});
+           $bklist = [ sort { $b->[1] <=> $a->[1] } @$bklist ];
 
-       if ($maxfiles) {
-           my @bklist = ();
-           my $dir = $opts->{dumpdir};
-           foreach my $fn (<$dir/${bkname}-*>) {
-               next if $fn eq $task->{tarfile};
-               if ($fn =~ m!/(${bkname}-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|tar))$!) {
-                   $fn = "$dir/$1"; # untaint
-                   my $t = timelocal ($7, $6, $5, $4, $3 - 1, $2 - 1900);
-                   push @bklist, [$fn, $t];
-               }
-           }
-       
-           @bklist = sort { $b->[1] <=> $a->[1] } @bklist;
-
-           my $ind = scalar (@bklist);
-
-           while (scalar (@bklist) >= $maxfiles) {
-               my $d = pop @bklist;
+           while (scalar (@$bklist) >= $maxfiles) {
+               my $d = pop @$bklist;
                debugmsg ('info', "delete old backup '$d->[0]'", $logfd);
                unlink $d->[0];
                my $logfn = $d->[0];
-               $logfn =~ s/\.(tgz|tar)$/\.log/;
+               $logfn =~ s/\.(tgz|((tar|vma)(\.(gz|lzo))?))$/\.log/;
                unlink $logfn;
            }
        }
@@ -906,8 +947,11 @@ sub exec_backup_task {
                    debugmsg ('info', "resume vm", $logfd);
                    $plugin->resume_vm ($task, $vmid);
                } else {
-                   debugmsg ('info', "restarting vm", $logfd);
-                   $plugin->start_vm ($task, $vmid);
+                   my $running = $plugin->vm_status($vmid);
+                   if (!$running) {
+                       debugmsg ('info', "restarting vm", $logfd);
+                       $plugin->start_vm ($task, $vmid);
+                   }
                } 
            };
            my $err = $@;
@@ -953,7 +997,7 @@ sub exec_backup_task {
 }
 
 sub exec_backup {
-    my ($self) = @_;
+    my ($self, $rpcenv, $authuser) = @_;
 
     my $opts = $self->{opts};
 
@@ -968,6 +1012,7 @@ sub exec_backup {
            my $vmlist = $plugin->vmlist();
            foreach my $vmid (sort @$vmlist) {
                next if grep { $_ eq  $vmid } @{$opts->{exclude}};
+               next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ], 1);
                push @$tasklist, { vmid => $vmid,  state => 'todo', plugin => $plugin };
            }
        }
@@ -981,6 +1026,7 @@ sub exec_backup {
                    last;
                }
            }
+           $rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ]);
            push @$tasklist, { vmid => $vmid,  state => 'todo', plugin => $plugin };
        }
     }
@@ -1045,10 +1091,11 @@ my $confdesc = {
        default => 1,
     },
     compress => {
-       type => 'boolean',
-       description => "Compress dump file (gzip).",
+       type => 'string',
+       description => "Compress dump file.",
        optional => 1,
-       default => 0,
+       enum => ['0', '1', 'gzip', 'lzo'],
+       default => 'lzo',
     },
     quiet => {
        type => 'boolean',
@@ -1134,6 +1181,12 @@ my $confdesc = {
        optional => 1,
        minimum => 1,
     },
+    remove => {
+       type => 'boolean',
+       description => "Remove old backup files if there are more than 'maxfiles' backup files.",
+       optional => 1,
+       default => 1,
+    },
 };
 
 sub option_exists {
@@ -1180,7 +1233,7 @@ sub command_line {
     }
 
     foreach my $p (keys %$param) {
-       next if $p eq 'id' || $p eq 'vmid' || $p eq 'starttime' || $p eq 'dow';
+       next if $p eq 'id' || $p eq 'vmid' || $p eq 'starttime' || $p eq 'dow' || $p eq 'stdout';
        my $v = $param->{$p};
        my $pd = $confdesc->{$p} || die "no such vzdump option '$p'\n";
        if ($p eq 'exclude-path') {