6 use PVE
::Exception
qw(raise_param_exc);
11 use POSIX
qw(strftime);
13 use PVE
::RPCEnvironment
;
15 use PVE
::Cluster
qw(cfs_read_file);
16 use PVE
::VZDump
::OpenVZ
;
19 use PVE
::JSONSchema
qw(get_standard_option);
21 my @posix_filesystems = qw(ext3 ext4 nfs nfs4 reiserfs xfs);
23 my $lockfile = '/var/run/vzdump.lock';
25 my $logdir = '/var/log/vzdump';
27 my @plugins = qw
(PVE
::VZDump
::OpenVZ
);
29 # Load available plugins
30 my $pveplug = "/usr/share/perl5/PVE/VZDump/QemuServer.pm";
32 eval { require $pveplug; };
34 PVE
::VZDump
::QemuServer-
>import ();
35 push @plugins, "PVE::VZDump::QemuServer";
50 my ($mtype, $msg, $logfd, $syslog) = @_;
56 my $pre = $debugstattxt->{$mtype} || $debugstattxt->{'err'};
58 my $timestr = strftime
("%b %d %H:%M:%S", CORE
::localtime);
60 syslog
($mtype eq 'info' ?
'info' : 'err', "$pre $msg") if $syslog;
62 foreach my $line (split (/\n/, $msg)) {
63 print STDERR
"$pre $line\n";
64 print $logfd "$timestr $pre $line\n" if $logfd;
69 my ($logfd, $cmdstr, %param) = @_;
73 debugmsg
('info', $line, $logfd);
76 PVE
::Tools
::run_command
($cmdstr, %param, logfunc
=> $logfunc);
82 my $cfg = cfs_read_file
('storage.cfg');
83 my $scfg = PVE
::Storage
::storage_config
($cfg, $storage);
84 my $type = $scfg->{type
};
86 die "can't use storage type '$type' for backup\n"
87 if (!($type eq 'dir' || $type eq 'nfs'));
88 die "can't use storage for backups - wrong content type\n"
89 if (!$scfg->{content
}->{backup
});
91 PVE
::Storage
::activate_storage
($cfg, $storage);
94 dumpdir
=> PVE
::Storage
::get_backup_dir
($cfg, $storage),
101 my $kb = $size / 1024;
104 return int ($kb) . "KB";
107 my $mb = $size / (1024*1024);
110 return int ($mb) . "MB";
113 return sprintf ("%.2fGB", $gb);
120 my $hours = int ($seconds/3600);
121 $seconds = $seconds - $hours*3600;
122 my $min = int ($seconds/60);
123 $seconds = $seconds - $min*60;
125 return sprintf ("%02d:%02d:%02d", $hours, $min, $seconds);
131 $str =~ s/^(.{990})/$1\n/mg; # reduce line length
149 foreach my $p (split (/:/, $ENV{PATH
})) {
156 die "unable to find command '$bin'\n";
163 foreach my $vmid (@vmids) {
164 die "ERROR: strange VM ID '${vmid}'\n" if $vmid !~ m/^\d+$/;
165 $vmid = int ($vmid); # remove leading zeros
174 sub read_vzdump_defaults
{
176 my $fn = "/etc/vzdump.conf";
182 lockwait
=> 3*60, # 3 hours
183 stopwait
=> 10, # 10 minutes
188 my $fh = IO
::File-
>new ("<$fn");
192 while (defined ($line = <$fh>)) {
193 next if $line =~ m/^\s*$/;
194 next if $line =~ m/^\#/;
196 if ($line =~ m/tmpdir:\s*(.*\S)\s*$/) {
198 } elsif ($line =~ m/dumpdir:\s*(.*\S)\s*$/) {
199 $res->{dumpdir
} = $1;
200 } elsif ($line =~ m/storage:\s*(\S+)\s*$/) {
201 $res->{storage
} = $1;
202 } elsif ($line =~ m/script:\s*(.*\S)\s*$/) {
204 } elsif ($line =~ m/bwlimit:\s*(\d+)\s*$/) {
205 $res->{bwlimit
} = int($1);
206 } elsif ($line =~ m/ionice:\s*([0-8])\s*$/) {
207 $res->{ionice
} = int($1);
208 } elsif ($line =~ m/lockwait:\s*(\d+)\s*$/) {
209 $res->{lockwait
} = int($1);
210 } elsif ($line =~ m/stopwait:\s*(\d+)\s*$/) {
211 $res->{stopwait
} = int($1);
212 } elsif ($line =~ m/size:\s*(\d+)\s*$/) {
213 $res->{size
} = int($1);
214 } elsif ($line =~ m/maxfiles:\s*(\d+)\s*$/) {
215 $res->{maxfiles
} = int($1);
216 } elsif ($line =~ m/exclude-path:\s*(.*)\s*$/) {
217 $res->{'exclude-path'} = PVE
::Tools
::split_args
($1);
218 } elsif ($line =~ m/mode:\s*(stop|snapshot|suspend)\s*$/) {
221 debugmsg
('warn', "unable to parse configuration file '$fn' - error at line " . $., undef, 1);
231 sub find_add_exclude
{
232 my ($self, $excltype, $value) = @_;
234 if (($excltype eq '-regex') || ($excltype eq '-files')) {
238 if ($excltype eq '-files') {
239 push @{$self->{findexcl
}}, "'('", '-not', '-type', 'd', '-regex' , "'$value'", "')'", '-o';
241 push @{$self->{findexcl
}}, "'('", $excltype , "'$value'", '-prune', "')'", '-o';
246 my ($self, $tasklist, $totaltime) = @_;
248 my $opts = $self->{opts
};
250 my $mailto = $opts->{mailto
};
252 return if !($mailto && scalar(@$mailto));
254 my $cmdline = $self->{cmdline
};
257 foreach my $task (@$tasklist) {
258 $ecount++ if $task->{state} ne 'ok';
259 chomp $task->{msg
} if $task->{msg
};
260 $task->{backuptime
} = 0 if !$task->{backuptime
};
261 $task->{size
} = 0 if !$task->{size
};
262 $task->{tarfile
} = 'unknown' if !$task->{tarfile
};
263 $task->{hostname
} = "VM $task->{vmid}" if !$task->{hostname
};
265 if ($task->{state} eq 'todo') {
266 $task->{msg
} = 'aborted';
270 my $stat = $ecount ?
'backup failed' : 'backup successful';
272 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
275 my $boundary = "----_=_NextPart_001_".int(time).$$;
278 foreach my $r (@$mailto) {
282 open (MAIL
,"|sendmail -B 8BITMIME $rcvrarg") ||
283 die "unable to open 'sendmail' - $!";
285 my $rcvrtxt = join (', ', @$mailto);
287 print MAIL
"Content-Type: multipart/alternative;\n";
288 print MAIL
"\tboundary=\"$boundary\"\n";
289 print MAIL "FROM
: vzdump backup tool
<root
>\n";
290 print MAIL "TO
: $rcvrtxt\n";
291 print MAIL "SUBJECT
: vzdump backup status
($hostname) : $stat\n";
293 print MAIL "This
is a multi-part message
in MIME format
.\n\n";
294 print MAIL "--$boundary\n";
296 print MAIL "Content-Type
: text
/plain
;\n";
297 print MAIL "\tcharset
=\"UTF8
\"\n";
298 print MAIL "Content-Transfer-Encoding
: 8bit
\n";
303 my $fill = ' '; # Avoid The Remove Extra Line Breaks Issue (MS Outlook)
305 print MAIL sprintf ("${fill
}%-10s
%-6s
%10s %10s %s\n", qw(VMID STATUS TIME SIZE FILENAME));
306 foreach my $task (@$tasklist) {
307 my $vmid = $task->{vmid
};
308 if ($task->{state} eq 'ok') {
310 print MAIL
sprintf ("${fill}%-10s %-6s %10s %10s %s\n", $vmid,
312 format_time
($task->{backuptime
}),
313 format_size
($task->{size
}),
316 print MAIL
sprintf ("${fill}%-10s %-6s %10s %8.2fMB %s\n", $vmid,
318 format_time
($task->{backuptime
}),
322 print MAIL
"${fill}\n";
323 print MAIL
"${fill}Detailed backup logs:\n";
324 print MAIL
"${fill}\n";
325 print MAIL
"$fill$cmdline\n";
326 print MAIL
"${fill}\n";
328 foreach my $task (@$tasklist) {
329 my $vmid = $task->{vmid
};
330 my $log = $task->{tmplog
};
332 print MAIL
"${fill}$vmid: no log available\n\n";
336 while (my $line = <TMP
>) { print MAIL encode8bit
("${fill}$vmid: $line"); }
338 print MAIL
"${fill}\n";
342 print MAIL
"\n--$boundary\n";
344 print MAIL
"Content-Type: text/html;\n";
345 print MAIL
"\tcharset=\"UTF8\"\n";
346 print MAIL
"Content-Transfer-Encoding: 8bit\n";
351 print MAIL
"<html><body>\n";
353 print MAIL
"<table border=1 cellpadding=3>\n";
355 print MAIL
"<tr><td>VMID<td>NAME<td>STATUS<td>TIME<td>SIZE<td>FILENAME</tr>\n";
359 foreach my $task (@$tasklist) {
360 my $vmid = $task->{vmid
};
361 my $name = $task->{hostname
};
363 if ($task->{state} eq 'ok') {
365 $ssize += $task->{size
};
367 print MAIL
sprintf ("<tr><td>%s<td>%s<td>OK<td>%s<td align=right>%s<td>%s</tr>\n",
369 format_time
($task->{backuptime
}),
370 format_size
($task->{size
}),
371 escape_html
($task->{tarfile
}));
373 print MAIL
sprintf ("<tr><td>%s<td>%s<td><font color=red>FAILED<td>%s<td colspan=2>%s</tr>\n",
375 $vmid, $name, format_time
($task->{backuptime
}),
376 escape_html
($task->{msg
}));
380 print MAIL
sprintf ("<tr><td align=left colspan=3>TOTAL<td>%s<td>%s<td></tr>",
381 format_time
($totaltime), format_size
($ssize));
383 print MAIL
"</table><br><br>\n";
384 print MAIL
"Detailed backup logs:<br>\n";
386 print MAIL
"<pre>\n";
387 print MAIL escape_html
($cmdline) . "\n";
390 foreach my $task (@$tasklist) {
391 my $vmid = $task->{vmid
};
392 my $log = $task->{tmplog
};
394 print MAIL
"$vmid: no log available\n\n";
398 while (my $line = <TMP
>) {
399 if ($line =~ m/^\S+\s\d+\s+\d+:\d+:\d+\s+(ERROR|WARN):/) {
400 print MAIL encode8bit
("$vmid: <font color=red>".
401 escape_html
($line) . "</font>");
403 print MAIL encode8bit
("$vmid: " . escape_html
($line));
409 print MAIL
"</pre>\n";
411 print MAIL
"</body></html>\n";
414 print MAIL
"\n--$boundary--\n";
420 my ($class, $cmdline, $opts, $skiplist) = @_;
426 check_bin
('sendmail');
430 check_bin
('umount');
431 check_bin
('cstream');
432 check_bin
('ionice');
434 if ($opts->{mode
} && $opts->{mode
} eq 'snapshot') {
435 check_bin
('lvcreate');
437 check_bin
('lvremove');
440 my $defaults = read_vzdump_defaults
();
442 foreach my $k (keys %$defaults) {
443 if ($k eq 'dumpdir' || $k eq 'storage') {
444 $opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir
}) &&
445 !defined ($opts->{storage
});
447 $opts->{$k} = $defaults->{$k} if !defined ($opts->{$k});
451 $opts->{dumpdir
} =~ s
|/+$|| if ($opts->{dumpdir
});
452 $opts->{tmpdir
} =~ s
|/+$|| if ($opts->{tmpdir
});
454 $skiplist = [] if !$skiplist;
455 my $self = bless { cmdline
=> $cmdline, opts
=> $opts, skiplist
=> $skiplist };
458 push @{$self->{findexcl
}}, "'('", '-regex' , "'^\\.\$'", "')'", '-o';
460 $self->find_add_exclude ('-type', 's'); # skip sockets
462 if ($defaults->{'exclude-path'}) {
463 foreach my $path (@{$defaults->{'exclude-path'}}) {
464 $self->find_add_exclude ('-regex', $path);
468 if ($opts->{'exclude-path'}) {
469 foreach my $path (@{$opts->{'exclude-path'}}) {
470 $self->find_add_exclude ('-regex', $path);
474 if ($opts->{stdexcludes
}) {
475 $self->find_add_exclude ('-files', '/var/log/.+');
476 $self->find_add_exclude ('-regex', '/tmp/.+');
477 $self->find_add_exclude ('-regex', '/var/tmp/.+');
478 $self->find_add_exclude ('-regex', '/var/run/.+pid');
481 foreach my $p (@plugins) {
483 my $pd = $p->new ($self);
485 push @{$self->{plugins
}}, $pd;
487 if (!$opts->{dumpdir
} && !$opts->{storage
} &&
488 ($p eq 'PVE::VZDump::OpenVZ')) {
489 $opts->{dumpdir
} = $pd->{dumpdir
};
493 if (!$opts->{dumpdir
} && !$opts->{storage
}) {
494 die "no dumpdir/storage specified - use option '--dumpdir' or option '--storage'\n";
497 if ($opts->{storage
}) {
498 my $info = storage_info
($opts->{storage
});
499 $opts->{dumpdir
} = $info->{dumpdir
};
500 } elsif ($opts->{dumpdir
}) {
501 die "dumpdir '$opts->{dumpdir}' does not exist\n"
502 if ! -d
$opts->{dumpdir
};
504 die "internal error";
507 if ($opts->{tmpdir
} && ! -d
$opts->{tmpdir
}) {
508 die "tmpdir '$opts->{tmpdir}' does not exist\n";
515 sub get_lvm_mapping
{
519 my $cmd = ['lvs', '--units', 'm', '--separator', ':', '--noheadings',
520 '-o', 'vg_name,lv_name,lv_size' ];
524 if ($line =~ m
|^\s
*(\S
+):(\S
+):(\d
+(\
.\d
+))[Mm
]$|) {
527 $devmapper->{"/dev/$vg/$lv"} = [$vg, $lv];
532 $devmapper->{"/dev/mapper/$qvg-$qlv"} = [$vg, $lv];
536 eval { PVE
::Tools
::run_command
($cmd, errfunc
=> sub {}, outfunc
=> $parser); };
545 my $cmd = [ 'df', '-P', '-T', '-B', '1', $dir];
551 if (my ($fsid, $fstype, $mp) = $line =~
552 m
|^(\S
+.*)\s
+(\S
+)\s
+\d
+\s
+\d
+\s
+\d
+\s
+\d
+%\s+(/.*)$|) {
561 eval { PVE
::Tools
::run_command
($cmd, errfunc
=> sub {}, outfunc
=> $parser); };
568 my ($dir, $mapping) = @_;
570 my $info = get_mount_info
($dir);
572 return undef if !$info;
574 my $dev = $info->{device
};
578 ($vg, $lv) = @{$mapping->{$dev}} if defined $mapping->{$dev};
580 return wantarray ?
($dev, $info->{mountpoint
}, $vg, $lv, $info->{fstype
}) : $dev;
586 my $maxwait = $self->{opts
}->{lockwait
} || $self->{lockwait
};
588 if (!open (SERVER_FLCK
, ">>$lockfile")) {
589 debugmsg
('err', "can't open lock on file '$lockfile' - $!", undef, 1);
593 if (flock (SERVER_FLCK
, LOCK_EX
|LOCK_NB
)) {
598 debugmsg
('err', "can't aquire lock '$lockfile' (wait = 0)", undef, 1);
602 debugmsg
('info', "trying to get global lock - waiting...", undef, 1);
605 alarm ($maxwait * 60);
607 local $SIG{ALRM
} = sub { alarm (0); die "got timeout\n"; };
609 if (!flock (SERVER_FLCK
, LOCK_EX
)) {
622 debugmsg
('err', "can't aquire lock '$lockfile' - $err", undef, 1);
626 debugmsg
('info', "got global lock", undef, 1);
629 sub run_hook_script
{
630 my ($self, $phase, $task, $logfd) = @_;
632 my $opts = $self->{opts
};
634 my $script = $opts->{script
};
638 my $cmd = "$script $phase";
640 $cmd .= " $task->{mode} $task->{vmid}" if ($task);
644 foreach my $ek (qw(vmtype dumpdir hostname tarfile logfile)) {
645 $ENV{uc($ek)} = $task->{$ek} if $task->{$ek};
648 run_command
($logfd, $cmd);
651 sub compressor_info
{
652 my ($opt_compress) = @_;
654 if (!$opt_compress || $opt_compress eq '0') {
656 } elsif ($opt_compress eq '1' || $opt_compress eq 'lzo') {
657 return ('lzop', 'lzo');
658 } elsif ($opt_compress eq 'gzip') {
659 return ('gzip', 'gz');
661 die "internal error - unknown compression option '$opt_compress'";
665 sub exec_backup_task
{
666 my ($self, $task) = @_;
668 my $opts = $self->{opts
};
670 my $vmid = $task->{vmid
};
671 my $plugin = $task->{plugin
};
673 my $vmstarttime = time ();
682 die "unable to find VM '$vmid'\n" if !$plugin;
684 my $vmtype = $plugin->type();
686 my $tmplog = "$logdir/$vmtype-$vmid.log";
688 my $lt = localtime();
690 my $bkname = "vzdump-$vmtype-$vmid";
691 my $basename = sprintf "${bkname}-%04d_%02d_%02d-%02d_%02d_%02d",
692 $lt->year + 1900, $lt->mon + 1, $lt->mday,
693 $lt->hour, $lt->min, $lt->sec;
695 my $logfile = $task->{logfile
} = "$opts->{dumpdir}/$basename.log";
698 my ($comp, $comp_ext) = compressor_info
($opts->{compress
});
699 if ($comp && $comp_ext) {
700 $ext .= ".${comp_ext}";
703 if ($opts->{stdout
}) {
704 $task->{tarfile
} = '-';
706 my $tarfile = $task->{tarfile
} = "$opts->{dumpdir}/$basename$ext";
707 $task->{tmptar
} = $task->{tarfile
};
708 $task->{tmptar
} =~ s/\.[^\.]+$/\.dat/;
709 unlink $task->{tmptar
};
712 $task->{vmtype
} = $vmtype;
714 if ($opts->{tmpdir
}) {
715 $task->{tmpdir
} = "$opts->{tmpdir}/vzdumptmp$$";
717 # dumpdir is posix? then use it as temporary dir
718 my $info = get_mount_info
($opts->{dumpdir
});
719 if ($vmtype eq 'qemu' ||
720 grep ($_ eq $info->{fstype
}, @posix_filesystems)) {
721 $task->{tmpdir
} = "$opts->{dumpdir}/$basename.tmp";
723 $task->{tmpdir
} = "/var/tmp/vzdumptmp$$";
724 debugmsg
('info', "filesystem type on dumpdir is '$info->{fstype}' -" .
725 "using $task->{tmpdir} for temporary files", $logfd);
729 rmtree
$task->{tmpdir
};
730 mkdir $task->{tmpdir
};
731 -d
$task->{tmpdir
} ||
732 die "unable to create temporary directory '$task->{tmpdir}'";
734 $logfd = IO
::File-
>new (">$tmplog") ||
735 die "unable to create log file '$tmplog'";
737 $task->{dumpdir
} = $opts->{dumpdir
};
739 $task->{tmplog
} = $tmplog;
743 debugmsg
('info', "Starting Backup of VM $vmid ($vmtype)", $logfd, 1);
745 $plugin->set_logfd ($logfd);
747 # test is VM is running
748 my ($running, $status_text) = $plugin->vm_status ($vmid);
750 debugmsg
('info', "status = ${status_text}", $logfd);
752 # lock VM (prevent config changes)
753 $plugin->lock_vm ($vmid);
755 $cleanup->{unlock
} = 1;
759 my $mode = $running ?
$opts->{mode
} : 'stop';
761 if ($mode eq 'snapshot') {
762 my %saved_task = %$task;
763 eval { $plugin->prepare ($task, $vmid, $mode); };
765 die $err if $err !~ m/^mode failure/;
766 debugmsg
('info', $err, $logfd);
767 debugmsg
('info', "trying 'suspend' mode instead", $logfd);
768 $mode = 'suspend'; # so prepare is called again below
769 %$task = %saved_task;
773 $task->{mode
} = $mode;
775 debugmsg
('info', "backup mode: $mode", $logfd);
777 debugmsg
('info', "bandwidth limit: $opts->{bwlimit} KB/s", $logfd)
780 debugmsg
('info', "ionice priority: $opts->{ionice}", $logfd);
782 if ($mode eq 'stop') {
784 $plugin->prepare ($task, $vmid, $mode);
786 $self->run_hook_script ('backup-start', $task, $logfd);
789 debugmsg
('info', "stopping vm", $logfd);
790 $vmstoptime = time ();
791 $self->run_hook_script ('pre-stop', $task, $logfd);
792 $plugin->stop_vm ($task, $vmid);
793 $cleanup->{restart
} = 1;
797 } elsif ($mode eq 'suspend') {
799 $plugin->prepare ($task, $vmid, $mode);
801 $self->run_hook_script ('backup-start', $task, $logfd);
803 if ($vmtype eq 'openvz') {
805 $plugin->copy_data_phase1 ($task, $vmid);
808 debugmsg
('info', "suspend vm", $logfd);
809 $vmstoptime = time ();
810 $self->run_hook_script ('pre-stop', $task, $logfd);
811 $plugin->suspend_vm ($task, $vmid);
812 $cleanup->{resume
} = 1;
814 if ($vmtype eq 'openvz') {
816 $plugin->copy_data_phase2 ($task, $vmid);
818 debugmsg
('info', "resume vm", $logfd);
819 $cleanup->{resume
} = 0;
820 $self->run_hook_script ('pre-restart', $task, $logfd);
821 $plugin->resume_vm ($task, $vmid);
822 my $delay = time () - $vmstoptime;
823 debugmsg
('info', "vm is online again after $delay seconds", $logfd);
826 } elsif ($mode eq 'snapshot') {
828 my $snapshot_count = $task->{snapshot_count
} || 0;
830 $self->run_hook_script ('pre-stop', $task, $logfd);
832 if ($snapshot_count > 1) {
833 debugmsg
('info', "suspend vm to make snapshot", $logfd);
834 $vmstoptime = time ();
835 $plugin->suspend_vm ($task, $vmid);
836 $cleanup->{resume
} = 1;
839 $plugin->snapshot ($task, $vmid);
841 $self->run_hook_script ('pre-restart', $task, $logfd);
843 if ($snapshot_count > 1) {
844 debugmsg
('info', "resume vm", $logfd);
845 $cleanup->{resume
} = 0;
846 $plugin->resume_vm ($task, $vmid);
847 my $delay = time () - $vmstoptime;
848 debugmsg
('info', "vm is online again after $delay seconds", $logfd);
852 die "internal error - unknown mode '$mode'\n";
855 # assemble archive image
856 $plugin->assemble ($task, $vmid);
860 if ($opts->{stdout
}) {
861 debugmsg
('info', "sending archive to stdout", $logfd);
862 $plugin->archive($task, $vmid, $task->{tmptar
}, $comp);
863 $self->run_hook_script ('backup-end', $task, $logfd);
867 debugmsg
('info', "creating archive '$task->{tarfile}'", $logfd);
868 $plugin->archive($task, $vmid, $task->{tmptar
}, $comp);
870 rename ($task->{tmptar
}, $task->{tarfile
}) ||
871 die "unable to rename '$task->{tmptar}' to '$task->{tarfile}'\n";
874 $task->{size
} = (-s
$task->{tarfile
}) || 0;
875 my $cs = format_size
($task->{size
});
876 debugmsg
('info', "archive file size: $cs", $logfd);
880 my $maxfiles = $opts->{maxfiles
};
884 my $dir = $opts->{dumpdir
};
885 foreach my $fn (<$dir/${bkname
}-*>) {
886 next if $fn eq $task->{tarfile
};
887 if ($fn =~ m!/(${bkname}-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|(tar(\.(gz|lzo))?)))$!) {
888 $fn = "$dir/$1"; # untaint
889 my $t = timelocal
($7, $6, $5, $4, $3 - 1, $2 - 1900);
890 push @bklist, [$fn, $t];
894 @bklist = sort { $b->[1] <=> $a->[1] } @bklist;
896 my $ind = scalar (@bklist);
898 while (scalar (@bklist) >= $maxfiles) {
900 debugmsg
('info', "delete old backup '$d->[0]'", $logfd);
903 $logfn =~ s/\.(tgz|(tar(\.(gz|lzo))?))$/\.log/;
908 $self->run_hook_script ('backup-end', $task, $logfd);
915 if ($cleanup->{unlock
}) {
916 eval { $plugin->unlock_vm ($vmid); };
920 eval { $plugin->cleanup ($task, $vmid) };
923 eval { $plugin->set_logfd (undef); };
926 if ($cleanup->{resume
} || $cleanup->{restart
}) {
928 $self->run_hook_script ('pre-restart', $task, $logfd);
929 if ($cleanup->{resume
}) {
930 debugmsg
('info', "resume vm", $logfd);
931 $plugin->resume_vm ($task, $vmid);
933 debugmsg
('info', "restarting vm", $logfd);
934 $plugin->start_vm ($task, $vmid);
941 my $delay = time () - $vmstoptime;
942 debugmsg
('info', "vm is online again after $delay seconds", $logfd);
947 eval { unlink $task->{tmptar
} if $task->{tmptar
} && -f
$task->{tmptar
}; };
950 eval { rmtree
$task->{tmpdir
} if $task->{tmpdir
} && -d
$task->{tmpdir
}; };
953 my $delay = $task->{backuptime
} = time () - $vmstarttime;
956 $task->{state} = 'err';
958 debugmsg
('err', "Backup of VM $vmid failed - $err", $logfd, 1);
960 eval { $self->run_hook_script ('backup-abort', $task, $logfd); };
963 $task->{state} = 'ok';
964 my $tstr = format_time
($delay);
965 debugmsg
('info', "Finished Backup of VM $vmid ($tstr)", $logfd, 1);
968 close ($logfd) if $logfd;
970 if ($task->{tmplog
} && $task->{logfile
}) {
971 system ("cp '$task->{tmplog}' '$task->{logfile}'");
974 eval { $self->run_hook_script ('log-end', $task); };
976 die $err if $err && $err =~ m/^interrupted by signal$/;
980 my ($self, $rpcenv, $authuser) = @_;
982 my $opts = $self->{opts
};
984 debugmsg
('info', "starting new backup job: $self->{cmdline}", undef, 1);
985 debugmsg
('info', "skip external VMs: " . join(', ', @{$self->{skiplist
}}))
986 if scalar(@{$self->{skiplist
}});
991 foreach my $plugin (@{$self->{plugins
}}) {
992 my $vmlist = $plugin->vmlist();
993 foreach my $vmid (sort @$vmlist) {
994 next if grep { $_ eq $vmid } @{$opts->{exclude
}};
995 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ], 1);
996 push @$tasklist, { vmid
=> $vmid, state => 'todo', plugin
=> $plugin };
1000 foreach my $vmid (sort @{$opts->{vmids
}}) {
1002 foreach my $pg (@{$self->{plugins
}}) {
1003 my $vmlist = $pg->vmlist();
1004 if (grep { $_ eq $vmid } @$vmlist) {
1009 $rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ]);
1010 push @$tasklist, { vmid
=> $vmid, state => 'todo', plugin
=> $plugin };
1014 my $starttime = time();
1018 $self->run_hook_script ('job-start');
1020 foreach my $task (@$tasklist) {
1021 $self->exec_backup_task ($task);
1022 $errcount += 1 if $task->{state} ne 'ok';
1025 $self->run_hook_script ('job-end');
1029 $self->run_hook_script ('job-abort') if $err;
1032 debugmsg
('err', "Backup job failed - $err", undef, 1);
1035 debugmsg
('info', "Backup job finished with errors", undef, 1);
1037 debugmsg
('info', "Backup job finished successfully", undef, 1);
1041 my $totaltime = time() - $starttime;
1043 eval { $self->$sendmail ($tasklist, $totaltime); };
1044 debugmsg
('err', $@) if $@;
1048 die "job errors\n" if $errcount;
1053 type
=> 'string', format
=> 'pve-vmid-list',
1054 description
=> "The ID of the VM you want to backup.",
1057 node
=> get_standard_option
('pve-node', {
1058 description
=> "Only run if executed on this node.",
1063 description
=> "Backup all known VMs on this host.",
1069 description
=> "Exclude temorary files and logs.",
1075 description
=> "Compress dump file.",
1077 enum
=> ['0', '1', 'gzip', 'lzo'],
1082 description
=> "Be quiet.",
1088 description
=> "Backup mode.",
1091 enum
=> [ 'snapshot', 'suspend', 'stop' ],
1094 type
=> 'string', format
=> 'pve-vmid-list',
1095 description
=> "exclude specified VMs (assumes --all)",
1099 type
=> 'string', format
=> 'string-alist',
1100 description
=> "exclude certain files/directories (regex).",
1104 type
=> 'string', format
=> 'string-list',
1110 description
=> "Store temporary files to specified directory.",
1115 description
=> "Store resulting files to specified directory.",
1120 description
=> "Use specified hook script.",
1123 storage
=> get_standard_option
('pve-storage-id', {
1124 description
=> "Store resulting file to this storage.",
1129 description
=> "LVM snapshot size im MB.",
1135 description
=> "Limit I/O bandwidth (KBytes per second).",
1141 description
=> "Set CFQ ionice priority.",
1148 description
=> "Maximal time to wait for the global lock (minutes).",
1154 description
=> "Maximal time to wait until a VM is stopped (minutes).",
1160 description
=> "Maximal number of backup files per VM.",
1168 return defined($confdesc->{$key});
1171 # add JSON properties for create and set function
1172 sub json_config_properties
{
1175 foreach my $opt (keys %$confdesc) {
1176 $prop->{$opt} = $confdesc->{$opt};
1182 sub verify_vzdump_parameters
{
1183 my ($param, $check_missing) = @_;
1185 raise_param_exc
({ all
=> "option conflicts with option 'vmid'"})
1186 if $param->{all
} && $param->{vmid
};
1188 raise_param_exc
({ exclude
=> "option conflicts with option 'vmid'"})
1189 if $param->{exclude
} && $param->{vmid
};
1191 $param->{all
} = 1 if defined($param->{exclude
});
1193 return if !$check_missing;
1195 raise_param_exc
({ vmid
=> "property is missing"})
1196 if !$param->{all
} && !$param->{vmid
};
1205 if ($param->{vmid
}) {
1206 $cmd .= " " . join(' ', PVE
::Tools
::split_list
($param->{vmid
}));
1209 foreach my $p (keys %$param) {
1210 next if $p eq 'id' || $p eq 'vmid' || $p eq 'starttime' || $p eq 'dow';
1211 my $v = $param->{$p};
1212 my $pd = $confdesc->{$p} || die "no such vzdump option '$p'\n";
1213 if ($p eq 'exclude-path') {
1214 foreach my $path (split(/\0/, $v || '')) {
1215 $cmd .= " --$p " . PVE
::Tools
::shellquote
($path);
1218 $cmd .= " --$p " . PVE
::Tools
::shellquote
($v) if defined($v) && $v ne '';