#!/usr/bin/perl -w use strict; use PVE::Exception qw(raise_param_exc);; use PVE::Tools qw(extract_param); use PVE::Cluster qw(cfs_register_file cfs_read_file); use PVE::SafeSyslog; use PVE::INotify; use PVE::RPCEnvironment; use PVE::CLIHandler; use PVE::JSONSchema qw(get_standard_option); use PVE::Storage; use PVE::VZDump; use Data::Dumper; # fixme: remove use base qw(PVE::CLIHandler); $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; initlog('vzdump'); die "please run as root\n" if $> != 0; PVE::INotify::inotify_init(); my $nodename = PVE::INotify::nodename(); my $rpcenv = PVE::RPCEnvironment->init('cli'); $rpcenv->init_request(); $rpcenv->set_language($ENV{LANG}); $rpcenv->set_user('root@pam'); __PACKAGE__->register_method ({ name => 'vzdump', path => 'vzdump', method => 'PUT', description => "Create backup.", parameters => { additionalProperties => 0, properties => { 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 => 'boolean', description => "Compress dump file (gzip).", optional => 1, default => 0, }, quiet => { type => 'boolean', description => "Be quiet.", optional => 1, default => 0, }, stop => { type => 'boolean', description => "Stop/Restart VM when running.", optional => 1, }, snapshot => { type => 'boolean', description => "Try to use (LVM) snapshots when running.", optional => 1, }, suspend => { type => 'boolean', description => "Suspend/resume VM when running", optional => 1, }, stdout => { type => 'boolean', description => "Write tar to stdout, not to a file.", optional => 1, }, exclude => { type => 'string', format => 'pve-vmid-list', description => "exclude specified VMs (assumes --all)", optional => 1, }, 'exclude-path' => { type => 'string', format => 'string-list', 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, }, }, }, returns => { type => 'string' }, code => sub { my ($param) = @_; my $rpcenv = PVE::RPCEnvironment::get(); my $user = $rpcenv->get_user(); if ($rpcenv->{type} ne 'cli') { raise_param_exc({ node => "option is only allowed on the command line interface."}) if $param->{node}; raise_param_exc({ stdout => "option is only allowed on the command line interface."}) if $param->{stdout}; } # by default we set --rsyncable for gzip local $ENV{GZIP} = "--rsyncable" if !$ENV{GZIP}; $param->{all} = 1 if defined($param->{exclude}); raise_param_exc({ all => "option conflicts with option 'vmid'"}) if $param->{all} && $param->{vmid}; raise_param_exc({ vmid => "property is missing"}) if !$param->{all} && !$param->{vmid}; # silent exit if we run on wrong node my $nodename = PVE::INotify::nodename(); exit(0) if $param->{node} && $param->{node} ne $nodename; # convert string lists to arrays my @vmids = PVE::Tools::split_list(extract_param($param, 'vmid')); my $cmdline = 'vzdump'; $cmdline .= ' ' . join(' ', @vmids) if scalar(@vmids); foreach my $p (keys %$param) { $cmdline .= " --$p $param->{$p}"; } $param->{vmids} = PVE::VZDump::check_vmids(@vmids) if !$param->{all}; my @exclude = PVE::Tools::split_list(extract_param($param, 'exclude')); $param->{exclude} = PVE::VZDump::check_vmids(@exclude); # exclude-path list need to be 0 separated my @expaths = split(/\0/, $param->{'exclude-path'} || ''); $param->{'exclude-path'} = @expaths; my @mailto = PVE::Tools::split_list(extract_param($param, 'mailto')); $param->{mailto} = [ @mailto ]; die "you can only backup a single VM with option --stdout\n" if $param->{stdout} && scalar(@vmids) != 1; my $vzdump = PVE::VZDump->new($cmdline, $param); my $worker = sub { $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { die "interrupted by signal\n"; }; $vzdump->getlock (); # only one process allowed if (defined($param->{ionice})) { if ($param->{ionice} > 7) { PVE::VZDump::run_command(undef, "ionice -c3 -p $$"); } else { PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$"); } } $vzdump->exec_backup(); }; open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout}; open STDERR, '>/dev/null' if $param->{quiet}; if ($rpcenv->{type} eq 'cli') { if ($param->{stdout}) { open my $saved_stdout, ">&STDOUT" || die "can't dup STDOUT: $!\n"; open STDOUT, '>&STDERR' || die "unable to redirect STDOUT: $!\n"; $param->{stdout} = $saved_stdout; } } return $rpcenv->fork_worker('vzdump', undef, $user, $worker); }}); my $cmddef = [ __PACKAGE__, 'vzdump', 'vmid', undef, sub { my $upid = shift; my $status = PVE::Tools::upid_read_status($upid); exit($status eq 'OK' ? 0 : -1); }]; push @ARGV, 'help' if !scalar(@ARGV); PVE::CLIHandler::handle_simple_cmd($cmddef, \@ARGV, undef, $0); exit 0; __END__ =head1 NAME vzdump - backup utility for virtual machine =head1 SYNOPSIS =include synopsis =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 ionize: PRI 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 777 --exclude-path C --exclude-path C (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 777 --suspend Backup all VMs and send notification mails to root and admin. # vzdump --all --suspend --mailto root --mailto admin Use LVM2 to create snapshots (no downtime). # vzdump 777 --dumpdir /mnt/backup --snapshot Backup more than one VM (selectively) # vzdump 101 102 103 --mailto root Backup all VMs excluding VM 101 and 102 # vzdump --suspend --exclude 101,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) =include pve_copyright