]> 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 dd6d6602ad8b6f1be95e5aa94551c4185462ec37..0bef3fdf420bf716475d5205e8975964b9a0793d 100644 (file)
@@ -1,38 +1,22 @@
 package PVE::VZDump;
 
-#    Copyright (C) 2007-2009 Proxmox Server Solutions GmbH
-#
-#    Copyright: vzdump is under GNU GPL, the GNU General Public License.
-#
-#    This program is free software; you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation; version 2 dated June, 1991.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program; if not, write to the
-#    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
-#    MA 02110-1301, USA.
-#
-#    Author: Dietmar Maurer <dietmar@proxmox.com>
-
 use strict;
 use warnings;
 use Fcntl ':flock';
-use Sys::Hostname;
-use Sys::Syslog;
+use PVE::Exception qw(raise_param_exc);
+use PVE::SafeSyslog;
 use IO::File;
 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;
 use Time::localtime;
 use Time::Local;
+use PVE::JSONSchema qw(get_standard_option);
 
 my @posix_filesystems = qw(ext3 ext4 nfs nfs4 reiserfs xfs);
 
@@ -84,112 +68,19 @@ sub debugmsg {
 sub run_command {
     my ($logfd, $cmdstr, %param) = @_;
 
-    my $timeout;
-    my $input;
-    my $output;
-
-    foreach my $p (keys %param) {
-       if ($p eq 'timeout') {
-           $timeout = $param{$p};
-       } elsif ($p eq 'input') {
-           $input = $param{$p};
-       } elsif ($p eq 'output') {
-           $output = $param{$p};
-       } else {
-           die "got unknown parameter '$p' for run_command\n";
-       }
-    }
-
-    my $reader = $output && $output =~ m/^>&/ ? $output : IO::File->new();
-    my $writer = $input && $input =~ m/^<&/ ? $input : IO::File->new();
-    my $error  = IO::File->new();
-
-    my $orig_pid = $$;
-
-    my $pid;
-    eval {
-       # suppress LVM warnings like: "File descriptor 3 left open";
-       local $ENV{LVM_SUPPRESS_FD_WARNINGS} = "1";
-
-       $pid = open3 ($writer, $reader, $error, ($cmdstr)) || die $!;
+    my $logfunc = sub {
+       my $line = shift;
+       debugmsg ('info', $line, $logfd);
     };
 
-    my $err = $@;
-
-    # catch exec errors
-    if ($orig_pid != $$) {
-       debugmsg ('err', "command '$cmdstr' failed - fork failed: $!", $logfd);
-       POSIX::_exit (1); 
-       kill ('KILL', $$); 
-    }
-
-    die $err if $err;
-
-    if (ref($writer)) {
-       print $writer $input if defined $input;
-       close $writer;
-    }
-
-    my $select = new IO::Select;
-    $select->add ($reader) if ref($reader);
-    $select->add ($error);
-
-    my ($ostream, $estream, $logout, $logerr) = ('', '', '', '');
-
-    while ($select->count) {
-       my @handles = $select->can_read ($timeout);
-
-       if (defined ($timeout) && (scalar (@handles) == 0)) {
-           die "command '$cmdstr' failed: timeout\n";
-       }
-
-       foreach my $h (@handles) {
-           my $buf = '';
-           my $count = sysread ($h, $buf, 4096);
-           if (!defined ($count)) {
-               waitpid ($pid, 0);
-               die "command '$cmdstr' failed: $!\n";
-           }
-           $select->remove ($h) if !$count;
-
-           if ($h eq $reader) {
-               $ostream .= $buf;
-               $logout .= $buf;
-               while ($logout =~ s/^([^\n]*\n)//s) {
-                   my $line = $1;
-                   debugmsg ('info', $line, $logfd);
-               }
-           } elsif ($h eq $error) {
-               $estream .= $buf;
-               $logerr .= $buf;
-               while ($logerr =~  s/^([^\n]*\n)//s) {
-                   my $line = $1;
-                   debugmsg ('info', $line, $logfd);
-               }
-           }
-       }
-    }
-
-    debugmsg ('info', $logout, $logfd);
-    debugmsg ('info', $logerr, $logfd);
-
-    waitpid ($pid, 0);
-    my $ec = ($? >> 8);
-
-    return $ostream if $ec == 24 && ($cmdstr =~ m|^(\S+/)?rsync\s|);
-
-    die "command '$cmdstr' failed with exit code $ec\n" if $ec;
-
-    return $ostream;
+    PVE::Tools::run_command($cmdstr, %param, logfunc => $logfunc);
 }
 
 sub storage_info {
     my $storage = shift;
 
-    eval { require PVE::Storage; };
-    die "unable to query storage info for '$storage' - $@\n" if $@;
-    my $cfg = PVE::Storage::load_config();
-    my $scfg = PVE::Storage::storage_config ($cfg, $storage);
+    my $cfg = cfs_read_file('storage.cfg');
+    my $scfg = PVE::Storage::storage_config($cfg, $storage);
     my $type = $scfg->{type};
  
     die "can't use storage type '$type' for backup\n" 
@@ -197,10 +88,11 @@ sub storage_info {
     die "can't use storage for backups - wrong content type\n" 
        if (!$scfg->{content}->{backup});
 
-    PVE::Storage::activate_storage ($cfg, $storage);
+    PVE::Storage::activate_storage($cfg, $storage);
 
     return {
-       dumpdir => $scfg->{path},
+       dumpdir => PVE::Storage::get_backup_dir($cfg, $storage),
+       maxfiles => $scfg->{maxfiles},
     };
 }
 
@@ -272,7 +164,7 @@ sub check_vmids {
     foreach my $vmid (@vmids) {
        die "ERROR: strange VM ID '${vmid}'\n" if $vmid !~ m/^\d+$/;
        $vmid = int ($vmid); # remove leading zeros
-       die "ERROR: got reserved VM ID '${vmid}'\n" if $vmid < 100;
+       next if !$vmid;
        push @$res, $vmid;
     }
 
@@ -322,6 +214,8 @@ sub read_vzdump_defaults {
            $res->{size} = int($1);
        } elsif ($line =~ m/maxfiles:\s*(\d+)\s*$/) {
            $res->{maxfiles} = int($1);
+       } elsif ($line =~ m/exclude-path:\s*(.*)\s*$/) {
+           $res->{'exclude-path'} = PVE::Tools::split_args($1); 
        } elsif ($line =~ m/mode:\s*(stop|snapshot|suspend)\s*$/) {
            $res->{mode} = $1;
        } else {
@@ -349,24 +243,6 @@ sub find_add_exclude {
     }
 }
 
-sub read_firstfile {
-    my $archive = shift;
-    
-    die "ERROR: file '$archive' does not exist\n" if ! -f $archive;
-
-    # try to detect archive type first
-    my $pid = open (TMP, "tar tf '$archive'|") ||
-       die "unable to open file '$archive'\n";
-    my $firstfile = <TMP>;
-    kill 15, $pid;
-    close TMP;
-
-    die "ERROR: archive contaions no data\n" if !$firstfile;
-    chomp $firstfile;
-
-    return $firstfile;
-}
-
 my $sendmail = sub {
     my ($self, $tasklist, $totaltime) = @_;
 
@@ -374,7 +250,7 @@ my $sendmail = sub {
 
     my $mailto = $opts->{mailto};
 
-    return if !$mailto;
+    return if !($mailto && scalar(@$mailto));
 
     my $cmdline = $self->{cmdline};
 
@@ -394,10 +270,9 @@ my $sendmail = sub {
 
     my $stat = $ecount ? 'backup failed' : 'backup successful';
 
-    my $hostname = `hostname -f` || hostname();
+    my $hostname = `hostname -f` || PVE::INotify::nodename();
     chomp $hostname;
 
-
     my $boundary = "----_=_NextPart_001_".int(time).$$;
 
     my $rcvrarg = '';
@@ -539,10 +414,11 @@ my $sendmail = sub {
     # end html part
     print MAIL "\n--$boundary--\n";
 
+    close(MAIL);
 };
 
 sub new {
-    my ($class, $cmdline, $opts) = @_;
+    my ($class, $cmdline, $opts, $skiplist) = @_;
 
     mkpath $logdir;
 
@@ -556,7 +432,7 @@ sub new {
     check_bin ('cstream');
     check_bin ('ionice');
 
-    if ($opts->{snapshot}) {
+    if ($opts->{mode} && $opts->{mode} eq 'snapshot') {
        check_bin ('lvcreate');
        check_bin ('lvs');
        check_bin ('lvremove');
@@ -564,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}) &&
@@ -573,20 +453,23 @@ sub new {
        }
     }
 
-    $opts->{mode} = 'stop' if $opts->{stop};
-    $opts->{mode} = 'suspend' if $opts->{suspend};
-    $opts->{mode} = 'snapshot' if $opts->{snapshot};
-
     $opts->{dumpdir} =~ s|/+$|| if ($opts->{dumpdir});
     $opts->{tmpdir} =~ s|/+$|| if ($opts->{tmpdir});
 
-    my $self = bless { cmdline => $cmdline, opts => $opts };
+    $skiplist = [] if !$skiplist;
+    my $self = bless { cmdline => $cmdline, opts => $opts, skiplist => $skiplist };
 
     #always skip '.'
     push @{$self->{findexcl}}, "'('", '-regex' , "'^\\.\$'", "')'", '-o';
 
     $self->find_add_exclude ('-type', 's'); # skip sockets
 
+    if ($defaults->{'exclude-path'}) {
+       foreach my $path (@{$defaults->{'exclude-path'}}) {
+           $self->find_add_exclude ('-regex', $path);
+       }
+    }
+
     if ($opts->{'exclude-path'}) {
        foreach my $path (@{$opts->{'exclude-path'}}) {
            $self->find_add_exclude ('-regex', $path);
@@ -605,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};
@@ -630,6 +509,8 @@ sub new {
        die "tmpdir '$opts->{tmpdir}' does not exist\n";
     }
 
+    $opts->{maxfiles} = $maxfiles if defined($maxfiles);
+
     return $self;
 
 }
@@ -638,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;
 }
@@ -661,30 +545,34 @@ 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 = split (/\s+/, $out);
+    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 {
     my ($dir, $mapping) = @_;
 
-    my $info = get_mount_info ($dir);
+    my $info = get_mount_info($dir);
 
     return undef if !$info;
    
@@ -758,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) = @_;
         
@@ -795,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} = '-';
@@ -814,7 +744,7 @@ sub exec_backup_task {
            $task->{tmpdir} = "$opts->{tmpdir}/vzdumptmp$$"; 
        } else {
            # dumpdir is posix? then use it as temporary dir
-           my $info = get_mount_info ($opts->{dumpdir});
+           my $info = get_mount_info($opts->{dumpdir});
            if ($vmtype eq 'qemu' || 
                grep ($_ eq $info->{fstype}, @posix_filesystems)) {
                $task->{tmpdir} = "$opts->{dumpdir}/$basename.tmp";
@@ -834,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;
@@ -924,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);
@@ -958,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";
@@ -976,29 +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)$!) {
-                   my $t = timelocal ($6, $5, $4, $3, $2 - 1, $1 - 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;
            }
        }
@@ -1028,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 = $@;
@@ -1075,12 +997,14 @@ sub exec_backup_task {
 }
 
 sub exec_backup {
-    my ($self) = @_;
+    my ($self, $rpcenv, $authuser) = @_;
 
     my $opts = $self->{opts};
 
     debugmsg ('info', "starting new backup job: $self->{cmdline}", undef, 1);
-
+    debugmsg ('info', "skip external VMs: " . join(', ', @{$self->{skiplist}}))
+       if scalar(@{$self->{skiplist}});
     my $tasklist = [];
 
     if ($opts->{all}) {
@@ -1088,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 };
            }
        }
@@ -1101,6 +1026,7 @@ sub exec_backup {
                    last;
                }
            }
+           $rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ]);
            push @$tasklist, { vmid => $vmid,  state => 'todo', plugin => $plugin };
        }
     }
@@ -1128,7 +1054,7 @@ sub exec_backup {
        if ($errcount) {
            debugmsg ('info', "Backup job finished with errors", undef, 1);
        } else {
-           debugmsg ('info', "Backup job finished successfuly", undef, 1);
+           debugmsg ('info', "Backup job finished successfully", undef, 1);
        }
     }
 
@@ -1136,6 +1062,190 @@ sub exec_backup {
 
     eval { $self->$sendmail ($tasklist, $totaltime); };
     debugmsg ('err', $@) if $@;
+
+    die $err if $err;
+
+    die "job errors\n" if $errcount; 
+}
+
+my $confdesc = {
+    vmid => {
+       type => 'string', format => 'pve-vmid-list',            
+       description => "The ID of the VM you want to backup.",
+       optional => 1,
+    },
+    node => get_standard_option('pve-node', { 
+       description => "Only run if executed on this node.",
+       optional => 1,
+    }),
+    all => {
+       type => 'boolean',
+       description => "Backup all known VMs on this host.",
+       optional => 1,
+       default => 0,
+    },
+    stdexcludes => {
+       type => 'boolean',
+       description => "Exclude temorary files and logs.",
+       optional => 1,
+       default => 1,
+    },
+    compress => {
+       type => 'string',
+       description => "Compress dump file.",
+       optional => 1,
+       enum => ['0', '1', 'gzip', 'lzo'],
+       default => 'lzo',
+    },
+    quiet => {
+       type => 'boolean',
+       description => "Be quiet.",
+       optional => 1,
+       default => 0,
+    },
+    mode => {
+       type => 'string',
+       description => "Backup mode.",
+       optional => 1,
+       default => 'stop',
+       enum => [ 'snapshot', 'suspend', 'stop' ],
+    },
+    exclude => {
+       type => 'string', format => 'pve-vmid-list',
+       description => "exclude specified VMs (assumes --all)",
+       optional => 1,
+    },
+    'exclude-path' => {
+       type => 'string', format => 'string-alist',
+       description => "exclude certain files/directories (regex).",
+       optional => 1,
+    },
+    mailto => {
+       type => 'string', format => 'string-list',
+       description => "",
+       optional => 1,
+    },
+    tmpdir => {
+       type => 'string',
+       description => "Store temporary files to specified directory.",
+       optional => 1,
+    },
+    dumpdir => {
+       type => 'string',
+       description => "Store resulting files to specified directory.",
+       optional => 1,
+    },
+    script => {
+       type => 'string',
+       description => "Use specified hook script.",
+       optional => 1,
+    },
+    storage => get_standard_option('pve-storage-id', {
+       description => "Store resulting file to this storage.",
+       optional => 1,
+    }),
+    size => {
+       type => 'integer',
+       description => "LVM snapshot size im MB.",
+       optional => 1,
+       minimum => 500,
+    },
+    bwlimit => {
+       type => 'integer',
+       description => "Limit I/O bandwidth (KBytes per second).",
+       optional => 1,
+       minimum => 0,
+    },
+    ionice => {
+       type => 'integer',
+       description => "Set CFQ ionice priority.",
+       optional => 1,
+       minimum => 0,
+       maximum => 8,
+    },
+    lockwait => {
+       type => 'integer',
+       description => "Maximal time to wait for the global lock (minutes).",
+       optional => 1,
+       minimum => 0,
+    },
+    stopwait => {
+       type => 'integer',
+       description => "Maximal time to wait until a VM is stopped (minutes).",
+       optional => 1,
+       minimum => 0,
+    },
+    maxfiles => {
+       type => 'integer',
+       description => "Maximal number of backup files per VM.",
+       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 {
+    my $key = shift;
+    return defined($confdesc->{$key});
+}
+
+# add JSON properties for create and set function
+sub json_config_properties {
+    my $prop = shift;
+
+    foreach my $opt (keys %$confdesc) {
+       $prop->{$opt} = $confdesc->{$opt};
+    }
+
+    return $prop;
+}
+
+sub verify_vzdump_parameters {
+    my ($param, $check_missing) = @_;
+
+    raise_param_exc({ all => "option conflicts with option 'vmid'"})
+       if $param->{all} && $param->{vmid};
+
+    raise_param_exc({ exclude => "option conflicts with option 'vmid'"})
+       if $param->{exclude} && $param->{vmid};
+
+    $param->{all} = 1 if defined($param->{exclude});
+
+    return if !$check_missing;
+
+    raise_param_exc({ vmid => "property is missing"})
+       if !$param->{all} && !$param->{vmid};
+
+}
+
+sub command_line {
+    my ($param) = @_;
+
+    my $cmd = "vzdump";
+
+    if ($param->{vmid}) {
+       $cmd .= " " . join(' ', PVE::Tools::split_list($param->{vmid}));
+    }
+
+    foreach my $p (keys %$param) {
+       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') {
+           foreach my $path (split(/\0/, $v || '')) {
+               $cmd .= " --$p " . PVE::Tools::shellquote($path);
+           }
+       } else {
+           $cmd .= " --$p " . PVE::Tools::shellquote($v) if defined($v) && $v ne '';
+       }
+    }
+
+    return $cmd;
 }
 
 1;