) { print MAIL encode8bit ("${fill}$vmid: $line"); }
close (TMP);
print MAIL "${fill}\n";
}
# end text part
print MAIL "\n--$boundary\n";
print MAIL "Content-Type: text/html;\n";
print MAIL "\tcharset=\"UTF8\"\n";
print MAIL "Content-Transfer-Encoding: 8bit\n";
print MAIL "\n";
# html part
print MAIL "\n";
print MAIL "\n";
print MAIL "VMID | NAME | STATUS | TIME | SIZE | FILENAME |
\n";
my $ssize = 0;
foreach my $task (@$tasklist) {
my $vmid = $task->{vmid};
my $name = $task->{hostname};
if ($task->{state} eq 'ok') {
$ssize += $task->{size};
print MAIL sprintf ("%s | %s | OK | %s | %s | %s |
\n",
$vmid, $name,
format_time($task->{backuptime}),
format_size ($task->{size}),
escape_html ($task->{tarfile}));
} else {
print MAIL sprintf ("%s | %s | FAILED | %s | %s |
\n",
$vmid, $name, format_time($task->{backuptime}),
escape_html ($task->{msg}));
}
}
print MAIL sprintf ("TOTAL | %s | %s | |
",
format_time ($totaltime), format_size ($ssize));
print MAIL "
\n";
print MAIL "Detailed backup logs:
\n";
print MAIL "
\n";
print MAIL "\n";
print MAIL escape_html($cmdline) . "\n";
print MAIL "\n";
foreach my $task (@$tasklist) {
my $vmid = $task->{vmid};
my $log = $task->{tmplog};
if (!$log) {
print MAIL "$vmid: no log available\n\n";
next;
}
open (TMP, "$log");
while (my $line = ) {
if ($line =~ m/^\S+\s\d+\s+\d+:\d+:\d+\s+(ERROR|WARN):/) {
print MAIL encode8bit ("$vmid: ".
escape_html ($line) . "");
} else {
print MAIL encode8bit ("$vmid: " . escape_html ($line));
}
}
close (TMP);
print MAIL "\n";
}
print MAIL "
\n";
print MAIL "\n";
# end html part
print MAIL "\n--$boundary--\n";
};
sub new {
my ($class, $cmdline, $opts) = @_;
mkpath $logdir;
check_bin ('cp');
check_bin ('df');
check_bin ('sendmail');
check_bin ('rsync');
check_bin ('tar');
check_bin ('mount');
check_bin ('umount');
check_bin ('cstream');
if ($opts->{snapshot}) {
check_bin ('lvcreate');
check_bin ('lvs');
check_bin ('lvremove');
}
my $defaults = read_vzdump_defaults();
foreach my $k (keys %$defaults) {
if ($k eq 'dumpdir' || $k eq 'storage') {
$opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir}) &&
!defined ($opts->{storage});
} else {
$opts->{$k} = $defaults->{$k} if !defined ($opts->{$k});
}
}
$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 };
#always skip '.'
push @{$self->{findexcl}}, "'('", '-regex' , "'^\\.\$'", "')'", '-o';
$self->find_add_exclude ('-type', 's'); # skip sockets
if ($opts->{'exclude-path'}) {
foreach my $path (@{$opts->{'exclude-path'}}) {
$self->find_add_exclude ('-regex', $path);
}
}
if ($opts->{stdexcludes}) {
$self->find_add_exclude ('-files', '/var/log/.+');
$self->find_add_exclude ('-regex', '/tmp/.+');
$self->find_add_exclude ('-regex', '/var/tmp/.+');
$self->find_add_exclude ('-regex', '/var/run/.+pid');
}
foreach my $p (@plugins) {
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";
}
if ($opts->{storage}) {
my $info = storage_info ($opts->{storage});
$opts->{dumpdir} = $info->{dumpdir};
} elsif ($opts->{dumpdir}) {
die "dumpdir '$opts->{dumpdir}' does not exist\n"
if ! -d $opts->{dumpdir};
} else {
die "internal error";
}
if ($opts->{tmpdir} && ! -d $opts->{tmpdir}) {
die "tmpdir '$opts->{tmpdir}' does not exist\n";
}
return $self;
}
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+))M$|) {
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);
}
return $devmapper;
}
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);
}
return undef if !$out;
my @res = split (/\s+/, $out);
return undef if scalar (@res) != 7;
return {
device => $res[0],
fstype => $res[1],
mountpoint => $res[6]
};
}
sub get_lvm_device {
my ($dir, $mapping) = @_;
my $info = get_mount_info ($dir);
return undef if !$info;
my $dev = $info->{device};
my ($vg, $lv);
($vg, $lv) = @{$mapping->{$dev}} if defined $mapping->{$dev};
return wantarray ? ($dev, $info->{mountpoint}, $vg, $lv, $info->{fstype}) : $dev;
}
sub getlock {
my ($self) = @_;
my $maxwait = $self->{opts}->{lockwait} || $self->{lockwait};
if (!open (SERVER_FLCK, ">>$lockfile")) {
debugmsg ('err', "can't open lock on file '$lockfile' - $!", undef, 1);
exit (-1);
}
if (flock (SERVER_FLCK, LOCK_EX|LOCK_NB)) {
return;
}
if (!$maxwait) {
debugmsg ('err', "can't aquire lock '$lockfile' (wait = 0)", undef, 1);
exit (-1);
}
debugmsg('info', "trying to get global lock - waiting...", undef, 1);
eval {
alarm ($maxwait * 60);
local $SIG{ALRM} = sub { alarm (0); die "got timeout\n"; };
if (!flock (SERVER_FLCK, LOCK_EX)) {
my $err = $!;
close (SERVER_FLCK);
alarm (0);
die "$err\n";
}
alarm (0);
};
alarm (0);
my $err = $@;
if ($err) {
debugmsg ('err', "can't aquire lock '$lockfile' - $err", undef, 1);
exit (-1);
}
debugmsg('info', "got global lock", undef, 1);
}
sub run_hook_script {
my ($self, $phase, $task, $logfd) = @_;
my $opts = $self->{opts};
my $script = $opts->{script};
return if !$script;
my $cmd = "$script $phase";
$cmd .= " $task->{mode} $task->{vmid}" if ($task);
local %ENV;
foreach my $ek (qw(vmtype dumpdir hostname tarfile logfile)) {
$ENV{uc($ek)} = $task->{$ek} if $task->{$ek};
}
run_command ($logfd, $cmd);
}
sub exec_backup_task {
my ($self, $task) = @_;
my $opts = $self->{opts};
my $vmid = $task->{vmid};
my $plugin = $task->{plugin};
my $vmstarttime = time ();
my $logfd;
my $cleanup = {};
my $vmstoptime = 0;
eval {
die "unable to find VM '$vmid'\n" if !$plugin;
my $vmtype = $plugin->type();
my $tmplog = "$logdir/$vmtype-$vmid.log";
my $lt = localtime();
my $bkname = "vzdump-$vmtype-$vmid";
my $basename = sprintf "${bkname}-%04d_%02d_%02d-%02d_%02d_%02d",
$lt->year + 1900, $lt->mon + 1, $lt->mday,
$lt->hour, $lt->min, $lt->sec;
my $logfile = $task->{logfile} = "$opts->{dumpdir}/$basename.log";
my $ext = $opts->{compress} ? '.tgz' : '.tar';
my $tarfile = $task->{tarfile} = "$opts->{dumpdir}/$basename$ext";
$task->{tmptar} = $task->{tarfile};
$task->{tmptar} =~ s/\.[^\.]+$/\.dat/;
$task->{vmtype} = $vmtype;
unlink $task->{tmptar};
if ($opts->{tmpdir}) {
$task->{tmpdir} = "$opts->{tmpdir}/vzdumptmp$$";
} else {
# dumpdir is posix? then use it as temporary dir
my $info = get_mount_info ($opts->{dumpdir});
if ($vmtype eq 'qemu' ||
grep ($_ eq $info->{fstype}, @posix_filesystems)) {
$task->{tmpdir} = "$opts->{dumpdir}/$basename.tmp";
} else {
$task->{tmpdir} = "/var/tmp/vzdumptmp$$";
debugmsg ('info', "filesystem type on dumpdir is '$info->{fstype}' -" .
"using $task->{tmpdir} for temporary files", $logfd);
}
}
rmtree $task->{tmpdir};
mkdir $task->{tmpdir};
-d $task->{tmpdir} ||
die "unable to create temporary directory '$task->{tmpdir}'";
$logfd = IO::File->new (">$tmplog") ||
die "unable to create log file '$tmplog'";
$task->{dumpdir} = $opts->{dumpdir};
$task->{tmplog} = $tmplog;
unlink $logfile;
debugmsg ('info', "Starting Backup of VM $vmid ($vmtype)", $logfd, 1);
$plugin->set_logfd ($logfd);
# test is VM is running
my ($running, $status_text) = $plugin->vm_status ($vmid);
debugmsg ('info', "status = ${status_text}", $logfd);
# lock VM (prevent config changes)
$plugin->lock_vm ($vmid);
$cleanup->{unlock} = 1;
# prepare
my $mode = $running ? $opts->{mode} : 'stop';
if ($mode eq 'snapshot') {
my %saved_task = %$task;
eval { $plugin->prepare ($task, $vmid, $mode); };
if (my $err = $@) {
die $err if $err !~ m/^mode failure/;
debugmsg ('info', $err, $logfd);
debugmsg ('info', "trying 'suspend' mode instead", $logfd);
$mode = 'suspend'; # so prepare is called again below
%$task = %saved_task;
}
}
$task->{mode} = $mode;
debugmsg ('info', "backup mode: $mode", $logfd);
debugmsg ('info', "bandwidth limit: $opts->{bwlimit} KB/s", $logfd)
if $opts->{bwlimit};
if ($mode eq 'stop') {
$plugin->prepare ($task, $vmid, $mode);
$self->run_hook_script ('backup-start', $task, $logfd);
if ($running) {
debugmsg ('info', "stopping vm", $logfd);
$vmstoptime = time ();
$self->run_hook_script ('pre-stop', $task, $logfd);
$plugin->stop_vm ($task, $vmid);
$cleanup->{restart} = 1;
}
} elsif ($mode eq 'suspend') {
$plugin->prepare ($task, $vmid, $mode);
$self->run_hook_script ('backup-start', $task, $logfd);
if ($vmtype eq 'openvz') {
# pre-suspend rsync
$plugin->copy_data_phase1 ($task, $vmid);
}
debugmsg ('info', "suspend vm", $logfd);
$vmstoptime = time ();
$self->run_hook_script ('pre-stop', $task, $logfd);
$plugin->suspend_vm ($task, $vmid);
$cleanup->{resume} = 1;
if ($vmtype eq 'openvz') {
# post-suspend rsync
$plugin->copy_data_phase2 ($task, $vmid);
debugmsg ('info', "resume vm", $logfd);
$cleanup->{resume} = 0;
$self->run_hook_script ('pre-restart', $task, $logfd);
$plugin->resume_vm ($task, $vmid);
my $delay = time () - $vmstoptime;
debugmsg ('info', "vm is online again after $delay seconds", $logfd);
}
} elsif ($mode eq 'snapshot') {
my $snapshot_count = $task->{snapshot_count} || 0;
$self->run_hook_script ('pre-stop', $task, $logfd);
if ($snapshot_count > 1) {
debugmsg ('info', "suspend vm to make snapshot", $logfd);
$vmstoptime = time ();
$plugin->suspend_vm ($task, $vmid);
$cleanup->{resume} = 1;
}
$self->run_hook_script ('pre-restart', $task, $logfd);
$plugin->snapshot ($task, $vmid);
if ($snapshot_count > 1) {
debugmsg ('info', "resume vm", $logfd);
$cleanup->{resume} = 0;
$plugin->resume_vm ($task, $vmid);
my $delay = time () - $vmstoptime;
debugmsg ('info', "vm is online again after $delay seconds", $logfd);
}
} else {
die "internal error - unknown mode '$mode'\n";
}
# assemble archive image
$plugin->assemble ($task, $vmid);
# produce archive
debugmsg ('info', "creating archive '$task->{tarfile}'", $logfd);
$plugin->archive ($task, $vmid, $task->{tmptar});
rename ($task->{tmptar}, $task->{tarfile}) ||
die "unable to rename '$task->{tmptar}' to '$task->{tarfile}'\n";
# determine size
$task->{size} = (-s $task->{tarfile}) || 0;
my $cs = format_size ($task->{size});
debugmsg ('info', "archive file size: $cs", $logfd);
# purge older backup
my $maxfiles = $opts->{maxfiles};
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;
debugmsg ('info', "delete old backup '$d->[0]'", $logfd);
unlink $d->[0];
my $logfn = $d->[0];
$logfn =~ s/\.(tgz|tar)$/\.log/;
unlink $logfn;
}
}
$self->run_hook_script ('backup-end', $task, $logfd);
};
my $err = $@;
if ($plugin) {
# clean-up
if ($cleanup->{unlock}) {
eval { $plugin->unlock_vm ($vmid); };
warn $@ if $@;
}
eval { $plugin->cleanup ($task, $vmid) };
warn $@ if $@;
eval { $plugin->set_logfd (undef); };
warn $@ if $@;
if ($cleanup->{resume} || $cleanup->{restart}) {
eval {
$self->run_hook_script ('pre-restart', $task, $logfd);
if ($cleanup->{resume}) {
debugmsg ('info', "resume vm", $logfd);
$plugin->resume_vm ($task, $vmid);
} else {
debugmsg ('info', "restarting vm", $logfd);
$plugin->start_vm ($task, $vmid);
}
};
my $err = $@;
if ($err) {
warn $err;
} else {
my $delay = time () - $vmstoptime;
debugmsg ('info', "vm is online again after $delay seconds", $logfd);
}
}
}
eval { unlink $task->{tmptar} if $task->{tmptar} && -f $task->{tmptar}; };
warn $@ if $@;
eval { rmtree $task->{tmpdir} if $task->{tmpdir} && -d $task->{tmpdir}; };
warn $@ if $@;
my $delay = $task->{backuptime} = time () - $vmstarttime;
if ($err) {
$task->{state} = 'err';
$task->{msg} = $err;
debugmsg ('err', "Backup of VM $vmid failed - $err", $logfd, 1);
eval { $self->run_hook_script ('backup-abort', $task, $logfd); };
} else {
$task->{state} = 'ok';
my $tstr = format_time ($delay);
debugmsg ('info', "Finished Backup of VM $vmid ($tstr)", $logfd, 1);
}
close ($logfd) if $logfd;
if ($task->{tmplog} && $task->{logfile}) {
system ("cp '$task->{tmplog}' '$task->{logfile}'");
}
die $err if $err && $err =~ m/^interrupted by signal$/;
}
sub exec_backup {
my ($self) = @_;
my $opts = $self->{opts};
debugmsg ('info', "starting new backup job: $self->{cmdline}", undef, 1);
my $tasklist = [];
if ($opts->{all}) {
foreach my $plugin (@{$self->{plugins}}) {
my $vmlist = $plugin->vmlist();
foreach my $vmid (sort @$vmlist) {
next if grep { $_ eq $vmid } @{$opts->{exclude}};
push @$tasklist, { vmid => $vmid, state => 'todo', plugin => $plugin };
}
}
} else {
foreach my $vmid (sort @{$opts->{vmids}}) {
my $plugin;
foreach my $pg (@{$self->{plugins}}) {
my $vmlist = $pg->vmlist();
if (grep { $_ eq $vmid } @$vmlist) {
$plugin = $pg;
last;
}
}
push @$tasklist, { vmid => $vmid, state => 'todo', plugin => $plugin };
}
}
my $starttime = time();
my $errcount = 0;
eval {
$self->run_hook_script ('job-start');
foreach my $task (@$tasklist) {
$self->exec_backup_task ($task);
$errcount += 1 if $task->{state} ne 'ok';
}
$self->run_hook_script ('job-end');
};
my $err = $@;
$self->run_hook_script ('job-abort') if $err;
if ($err) {
debugmsg ('err', "Backup job failed - $err", undef, 1);
} else {
if ($errcount) {
debugmsg ('info', "Backup job finished with errors", undef, 1);
} else {
debugmsg ('info', "Backup job finished successfuly", undef, 1);
}
}
my $totaltime = time() - $starttime;
eval { $self->$sendmail ($tasklist, $totaltime); };
debugmsg ('err', $@) if $@;
}
1;
vzdump_2010-05-25/hook-script.pl 0000755 0000000 0000000 00000002212 11376661437 015026 0 ustar root root #!/usr/bin/perl -w
# example hook script for vzdump (--script option)
use strict;
print "HOOK: " . join (' ', @ARGV) . "\n";
my $phase = shift;
if ($phase eq 'job-start' ||
$phase eq 'job-end' ||
$phase eq 'job-abort') {
# do what you want
} elsif ($phase eq 'backup-start' ||
$phase eq 'backup-end' ||
$phase eq 'backup-abort' ||
$phase eq 'pre-stop' ||
$phase eq 'pre-restart') {
print "HOOK: " . join (' ', @ARGV) . "\n";
my $mode = shift; # stop/suspend/snapshot
my $vmid = shift;
my $vmtype = $ENV{VMTYPE}; # openvz/qemu
my $dumpdir = $ENV{DUMPDIR};
my $hostname = $ENV{HOSTNAME};
my $tarfile = $ENV{TARFILE};
my $logfile = $ENV{LOGFILE};
print "HOOK-ENV: vmtype=$vmtype;dumpdir=$dumpdir;hostname=$hostname;tarfile=$tarfile;logfile=$logfile\n";
# example: copy resulting files to another host using scp
#if ($phase eq 'backup-end') {
# system ("scp $tarfile $logfile backup-host:/backup-dir") == 0 ||
# die "copy to backup-host failed";
# unlink $tarfile;
# unlink $logfile;
#}
} else {
die "got unknown phase '$phase'";
}
exit (0);
vzdump_2010-05-25/changelog.Debian 0000644 0000000 0000000 00000004675 11376661437 015276 0 ustar root root vzdump (1.2-6) unstable; urgency=low
* vzrestore: new --force option to overwrite existing data
-- Proxmox Support Team Thu, 20 May 2010 11:13:48 +0200
vzdump (1.2-5) unstable; urgency=low
* re-added missing liblockfile-simple-perl
-- Proxmox Support Team Fri, 23 Oct 2009 08:55:00 +0200
vzdump (1.2-4) unstable; urgency=low
* correctly detect rsync command (avoid backup to fail when files
vanished before they could be transferred)
-- Proxmox Support Team Thu, 22 Oct 2009 10:01:32 +0200
vzdump (1.2-3) unstable; urgency=low
* correctly handle '$VEID' macro in config files (VE_ROOT, VE_PRIVATE).
* don't cross filesystem boundaries with OpenVZ backups (bind mount are
no longer included).
* support hook scripts (new --script option)
* new naming scheme - include date/time into file names.
-- Proxmox Support Team Mon, 12 Oct 2009 12:50:18 +0200
vzdump (1.2-2) unstable; urgency=low
* new storage option for PVE
-- Proxmox Support Team Mon, 21 Sep 2009 11:36:01 +0200
vzdump (1.2-1) unstable; urgency=low
* complete rewrite
-- Proxmox Support Team Thu, 1 Sep 2009 11:00:06 +0200
vzdump (1.0-2) unstable; urgency=low
* bug fix in config parser
-- Proxmox Support Team Thu, 28 Jun 2007 11:38:06 +0200
vzdump (1.0-1) stable; urgency=low
* set version number to 1.0, because we are quite stable
* added copyright notice to man page
-- Proxmox Support Team Thu, 10 May 2007 09:50:01 +0200
vzdump (0.4-1) unstable; urgency=low
* also read config of VEs with ID > 999
-- Proxmox Support Team Mon, 26 Mar 2007 09:05:28 +0200
vzdump (0.3-1) unstable; urgency=low
* merged in patch from Kir Kolyshkin (rpm related changes)
-- Proxmox Support Team Thu, 22 Mar 2007 08:35:55 +0100
vzdump (0.2-2) unstable; urgency=low
* changed package dependency (perl > 5.6.0-16, vzdump)
-- Proxmox Support Team Wed, 21 Mar 2007 10:42:10 +0100
vzdump (0.2-1) stable; urgency=low
* dm2 snapshot support
* many bug fixes
* new manual page
-- Proxmox Support Team Mon, 19 Mar 2007 11:10:25 +0100
vzdump (0.1-1) stable; urgency=low
* first release
-- Proxmox Support Team Wed, 7 Mar 2007 16:20:48 +0100
vzdump_2010-05-25/vzdump 0000755 0000000 0000000 00000024237 11376661437 013512 0 ustar root root #!/usr/bin/perl -w
#
# 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
#
use strict;
use Getopt::Long;
use Sys::Syslog;
use PVE::VZDump;
$ENV{LANG} = "C"; # avoid locale related issues/warnings
# by default we set --rsyncable for gzip
$ENV{GZIP} = "--rsyncable" if !$ENV{GZIP};
# just to be sure that we have a resonable path
$ENV{PATH} = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin";
my $cmdline = join (' ', 'vzdump', @ARGV);
openlog ('vzdump', 'cons,pid', 'daemon');
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
die "interrupted by signal\n";
};
my @std_opts = (
'all',
'exclude=s@',
'exclude-path=s@',
'stdexcludes',
'compress',
'mailto=s@',
'quiet',
'stop',
'suspend',
'snapshot',
'size=i',
'node=i',
'bwlimit=i',
'lockwait=i',
'stopwait=i',
'tmpdir=s',
'dumpdir=s',
'maxfiles=i',
'script=s',
'storage=s',
);
sub print_usage {
my $msg = shift;
print STDERR "ERROR: $msg\n\n" if $msg;
print STDERR "usage: $0 OPTIONS [--all | VMID]\n\n";
print STDERR "\t--exclude VMID\t\texclude VMID (assumes --all)\n";
print STDERR "\t--exclude-path REGEX\texclude certain files/directories\n"; print STDERR "\t--stdexcludes\t\texclude temorary files and logs\n\n";
print STDERR "\t--compress\t\tcompress dump file (gzip)\n";
print STDERR "\t--dumpdir DIR\t\tstore resulting files in DIR\n";
print STDERR "\t--maxfiles N\t\tmaximal number of backup files per VM\n";
print STDERR "\t--script FILENAME\texecute hook script\n";
print STDERR "\t--storage STORAGE_ID\tstore resulting files to STORAGE_ID (PVE only)\n";
print STDERR "\t--tmpdir DIR\t\tstore temporary files in DIR\n\n";
print STDERR "\t--mailto EMAIL\t\tsend notification mail to EMAIL.\n";
print STDERR "\t--quiet\t\t\tbe quiet.\n";
print STDERR "\t--stop\t\t\tstop/start VM if running\n";
print STDERR "\t--suspend\t\tsuspend/resume VM when running\n";
print STDERR "\t--snapshot\t\tuse LVM snapshot when running\n";
print STDERR "\t--size MB\t\tLVM snapshot size\n\n";
print STDERR "\t--node CID\t\tonly run on pve cluster node CID\n";
print STDERR "\t--lockwait MINUTES\tmaximal time to wait for the global lock\n";
print STDERR "\t--stopwait MINUTES\tmaximal time to wait until a VM is stopped\n";
print STDERR "\t--bwlimit KBPS\t\tlimit I/O bandwidth; KBytes per second\n\n";
print STDERR "\n";
}
my $opts = {};
if (!GetOptions ($opts, @std_opts)) {
print_usage ();
exit (-1);
}
if ($opts->{node}) {
PVE::VZDump::check_bin ('pveca');
my $info = `pveca -i`;
chomp $info;
die "unable to parse pveca info" if $info !~ m/^(\d+)\s+\S+\s+\S+\s+\S+$/;
my $cid = $1;
# silent exit if we run on wrong node
exit (0) if $cid != $opts->{node};
}
$opts->{all} = 1 if $opts->{exclude};
if ($opts->{all} && $#ARGV >= 0) {
print_usage ();
exit (-1);
}
if (!$opts->{all} && $#ARGV == -1) {
print_usage ();
exit (-1);
}
if ($opts->{quiet}) {
open STDOUT, '>/dev/null';
open STDERR, '>/dev/null';
}
$opts->{vmids} = PVE::VZDump::check_vmids (@ARGV) if !$opts->{all};
$opts->{exclude} = PVE::VZDump::check_vmids (@{$opts->{exclude}}) if $opts->{exclude};
my $vzdump = PVE::VZDump->new ($cmdline, $opts);
$vzdump->getlock (); # only one process allowed
# parameters are OK - now start real work and log everything
eval {
$vzdump->exec_backup();
};
my $err = $@;
if ($err) {
PVE::VZDump::debugmsg ('err', $err, undef, 1);
exit (-1);
}
exit 0;
__END__
=head1 NAME
vzdump - backup utility for virtual machine
=head1 SYNOPSIS
vzdump OPTIONS [--all | ]
--exclude VMID exclude VMID (assumes --all)
--exclude-path REGEX exclude certain files/directories. You
can use this option more than once to specify
multiple exclude paths
--stdexcludes exclude temporary files and logs
--compress compress dump file (gzip)
--storage STORAGE_ID store resulting files to STORAGE_ID (PVE only)
--script execute hook script
--dumpdir DIR store resulting files in DIR
--maxfiles N maximal number of backup files per VM.
--tmpdir DIR store temporary files in DIR. --suspend and --stop
are using this directory to store a copy of the VM.
--mailto EMAIL send notification mail to EMAIL. You can use
this option more than once to specify multiple
receivers
--stop stop/start VM if running
--suspend suspend/resume VM when running
--snapshot use LVM snapshot when running
--size MB LVM snapshot size (default 1024)
--bwlimit KBPS limit I/O bandwidth; KBytes per second
--lockwait MINUTES maximal time to wait for the global
lock. vzdump uses a global lock file to make
sure that only one instance is running
(running several instance puts too much load
on a server). Default is 180 (3 hours).
--stopwait MINUTES maximal time to wait until a VM is stopped.
=head1 DESCRIPTION
vzdump is an utility to make consistent snapshots of running virtual
machines (VMs). It basically creates a tar archive of the VM private area,
which also includes the VM configuration files. vzdump currently
supports OpenVZ and QemuServer VMs.
There are several ways to provide consistency:
=over 2
=item C mode
Stop the VM during backup. This results in a very long downtime.
=item C mode
For OpenVZ, this mode uses rsync to copy the VM to a temporary
location (see option --tmpdir). Then the VM is suspended and a second
rsync copies changed files. After that, the VM is started (resume)
again. This results in a minimal downtime, but needs additional space
to hold the VM copy.
For QemuServer, this mode work like C mode, but uses
suspend/resume instead of stop/start.
=item C mode
This mode uses LVM2 snapshots. There is no downtime, but snapshot mode
needs LVM2 and some free space on the corresponding volume group to
create the LVM snapshot.
=back
=head1 BACKUP FILE NAMES
Newer version of vzdump encodes the virtual machine type and the
backup time into the filename, for example
vzdump-openvz-105-2009_10_09-11_04_43.tar
That way it is possible to store several backup into the same
directory. The parameter C can be used to specify the maximal
number of backups to keep.
=head1 RESTORE
The resulting tar files can be restored with the following programs.
=over 1
=item vzrestore: OpenVZ restore utility
=item qmrestore: QemuServer restore utility
=back
For details see the corresponding manual pages.
=head1 CONFIGURATION
Global configuration is stored in /etc/vzdump.conf.
tmpdir: DIR
dumpdir: DIR
storage: STORAGE_ID
mode: snapshot|suspend|stop
bwlimit: KBPS
lockwait: MINUTES
stopwait: MINUTES
size: MB
maxfiles: N
script: FILENAME
=head1 HOOK SCRIPT
You can specify a hook script with option C<--script>. This script is called at various phases of the backup process, with parameters accordingly set. You can find an example in the documentation directory (C).
=head1 EXCLUSIONS (OpenVZ only)
vzdump skips the following files wit option --stdexcludes
/var/log/.+
/tmp/.+
/var/tmp/.+
/var/run/.+pid
You can manually specify exclude paths, for example:
> vzdump --exclude-path C --exclude-path C 777
(only excludes tmp directories)
Configuration files are also stored inside the backup archive (/etc/vzdump), and will be correctly restored.
=head1 LIMITATIONS
VZDump does not save ACLs.
=head1 EXAMPLES
Simply dump VM 777 - no snapshot, just archive the VM private area and configuration files to the default dump directory (usually /vz/dump/).
> vzdump 777
Use rsync and suspend/resume to create an snapshot (minimal downtime).
> vzdump --suspend 777
Backup all VMs and send notification mails to root.
> vzdump --suspend --all --mailto root
Use LVM2 to create snapshots (no downtime).
> vzdump --dumpdir /mnt/backup --snapshot 777
Backup all VMs excluding VM 101 and 102
> vzdump --suspend --exclude 101 --exclude 102
Restore an OpenVZ machine to VM 600
> vzrestore /mnt/backup/vzdump-openvz-777.tar 600
Restore an Qemu/KVM machine to VM 601
> qmrestore /mnt/backup/vzdump-qemu-888.tar 601
=head1 SEE ALSO
vzrestore(1) qmrestore(1)
=head1 AUTHOR
Dietmar Maurer