commit
b810c3c7d6
3 changed files with 796 additions and 0 deletions
@ -0,0 +1,80 @@ |
||||
This script allows you to backup Virtual Machines managed by libvirt. |
||||
It has only be tested with KVM based VM |
||||
This script will dump (or mount as a set of chunks): |
||||
* each block devices |
||||
* optionnally the memory (if --state flag is given) |
||||
* the XML description of the VM |
||||
|
||||
These files are writen in a temporary backup dir. Everything is done |
||||
in order to minimize donwtime of the guest. For example, it takes |
||||
a snapshot of the block devices (if backed with LVM) so the guest is |
||||
just paused for a couple of seconds. Once this is done, the guest is |
||||
resumed, and the script starts to dump the snapshot. |
||||
|
||||
Once a backup is finished, you'll have several files in the backup |
||||
directory. Let's take an example with a VM called my_vm which has |
||||
two virtual disks: hda and hdb. You have passed the --state flag: |
||||
* my_vm.lock: lock file to prevent another backup to run at the same time |
||||
* my_vm.xml: this file is the XML description of the VM (for libvirt configuraiton) |
||||
* my_vm_hda.img: this file is an image of the hda drive of the guest |
||||
* my_vm_hdb.img: this file is an image of the hdb drive of the guest |
||||
* my_vm.state: this is a dump of the memory (result of virsh save my_vm my_vm.state) |
||||
|
||||
This script was made to be ran with BackupPC pre/post commands. |
||||
In pre-backup, you dump everything (or mount as a set of chunks), then, backuppc backups, |
||||
compress, pools etc... the dumped file. Eventually, when the backup is finished |
||||
The script is called with the --action=cleanup flag, which cleanups everything. |
||||
(remove the temporary files, umount the fuse mount points if any etc.) |
||||
|
||||
Some examples: |
||||
|
||||
Backup the VM named mail01 and devsrv. Also dump the memory. |
||||
Exclude any virtual disk attached as vdb or hdb and on the fly |
||||
compress the dumped disks (uses gzip by default) |
||||
|
||||
virt-backup --dump --vm=mail01,devsrv --state --exclude=vdb,hdb --compress |
||||
|
||||
|
||||
Remove all the files related to mail01 VM in the backup directory |
||||
|
||||
virt-backup --cleanup --vm=mail01 |
||||
|
||||
|
||||
Backup devsrv, use 10G for LVM snapshots (if available), do not dump the memory |
||||
(the guest will just be paused while we take a snapshot) |
||||
Keep the lock file present after the dump |
||||
|
||||
virt-backup --dump --vm=devsrv --snapsize=10G --keep-lock |
||||
|
||||
|
||||
Backup devsrv, and disable LVM snapshots |
||||
|
||||
virt-backup --dump --vm=devsrv --no-snapshot |
||||
|
||||
|
||||
Backup mail01, and enable debug (verbose output) |
||||
|
||||
virt-backup --dump --vm=mail01 --debug |
||||
|
||||
|
||||
Backup winprd, shutdown before taking the backup |
||||
Dont wait more than 5 minutes for the shutdown to complete |
||||
restart the VM once the backup is finished |
||||
|
||||
virt-backup --action=dump --shutdown --shutdown-timeout=300 --vm=winprd |
||||
|
||||
|
||||
Don't dump, but mount as a set of chunks the disks of vm mail01 |
||||
|
||||
virt-backup --action=chunkmount --vm=mail01 |
||||
|
||||
The idea here is to expose the big blocks/files which represent the VM disks |
||||
as small chunks (default is 256kB), then, you can use your favorite backup script/software |
||||
to backup /var/lib/libvirt/backup/vm_name/ where you want |
||||
This lets you create incremential backups of VM disks, which can save |
||||
a lot of space, a lot of bandwidth, and will also be much more efficient |
||||
with rsync based backup scripts (because rsync doesn't handle huge files very well |
||||
but if very efficient with a lot of small files) |
||||
The cleanup routine (--cleanup or --action=cleanup) will unmount all |
||||
the chunkfs mount points |
||||
|
@ -0,0 +1,644 @@ |
||||
#!/usr/bin/perl -w |
||||
|
||||
# AUTHOR |
||||
# Daniel Berteaud <daniel@firewall-services.com> |
||||
# |
||||
# COPYRIGHT |
||||
# Copyright (C) 2009-2011 Daniel Berteaud |
||||
# |
||||
# 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; either version 2 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
|
||||
|
||||
# See README for documentation and examples |
||||
|
||||
|
||||
### CHANGES |
||||
# * 12/01/2012 |
||||
# - Add shutdown and shutdown-timeout options (patch from Asher Schaffer) |
||||
# |
||||
# * 31/07/2011 |
||||
# - Add new option to mount images as chunked files using chunkfs |
||||
# - Use 512k chunk size for LVM snapshots which should reduce performance penalty during backups |
||||
# |
||||
# * 26/03/2010 |
||||
# - Initial packaged version |
||||
|
||||
use XML::Simple; |
||||
use Sys::Virt; |
||||
use Getopt::Long; |
||||
|
||||
# Set umask |
||||
umask(022); |
||||
|
||||
# Some constant |
||||
|
||||
our %opts = (); |
||||
our @vms = (); |
||||
our @excludes = (); |
||||
our @disks = (); |
||||
|
||||
# Sets some defaults values |
||||
|
||||
# What to run. The default action is to dump |
||||
$opts{action} = 'dump'; |
||||
# Where backups will be stored. This directory must already exists |
||||
$opts{backupdir} = '/var/lib/libvirt/backup'; |
||||
# Size of LVM snapshots (which will be used to backup VM with minimum downtown |
||||
# if the VM store data directly on a LV) |
||||
$opts{snapsize} = '5G'; |
||||
# If we should also dump the VM state (dump the mémory, equivalent of virsh save) |
||||
$opts{state} = 0; |
||||
# Debug |
||||
$opts{debug} = 0; |
||||
# Let the lock file present after the dump is finisehd |
||||
$opts{keeplock} = 0; |
||||
# Should we try to create LVM snapshots during the dump ? |
||||
$opts{snapshot} = 1; |
||||
# Libvirt URI to connect to |
||||
$opts{connect} = "qemu:///system"; |
||||
# Compression used with the dump action (the compression is done on the fly) |
||||
$opts{compress} = 'none'; |
||||
# lvcreate path |
||||
$opts{lvcreate} = '/usr/sbin/lvcreate -c 512'; |
||||
# lvremove path |
||||
$opts{lvremove} = '/usr/sbin/lvremove'; |
||||
# chunkfs path |
||||
$opts{chunkfs} = '/usr/bin/chunkfs'; |
||||
# Size of chunks to use with chunkfs or or blocks with dd in bytes (default to 256kB) |
||||
$opts{blocksize} = '262144'; |
||||
# nice may be used to reduce CPU priority of compression processes |
||||
$opts{nice} = 'nice -n 19'; |
||||
# ionice may be used to reduce disk access priority of dump/chunkfs processes |
||||
# which can be quite I/O intensive. This only works if your storage |
||||
# uses the CFQ scheduler (which is the default on EL) |
||||
$opts{ionice} = 'ionice -c 2 -n 7'; |
||||
|
||||
$opts{shutdown} = 0; |
||||
$opts{shutdown_time} = 300; |
||||
$opts{livebackup} = 1; |
||||
$opts{wasrunning} = 1; |
||||
|
||||
# get command line arguments |
||||
GetOptions( |
||||
"debug" => \$opts{debug}, |
||||
"keep-lock" => \$opts{keeplock}, |
||||
"state" => \$opts{state}, |
||||
"snapsize=s" => \$opts{snapsize}, |
||||
"backupdir=s" => \$opts{backupdir}, |
||||
"vm=s" => \@vms, |
||||
"action=s" => \$opts{action}, |
||||
"cleanup" => \$opts{cleanup}, |
||||
"dump" => \$opts{dump}, |
||||
"unlock" => \$opts{unlock}, |
||||
"connect=s" => \$opts{connect}, |
||||
"snapshot!" => \$opts{snapshot}, |
||||
"compress:s" => \$opts{compress}, |
||||
"exclude=s" => \@excludes, |
||||
"blocksize=s" => \$opts{blocksize}, |
||||
"shutdown" => \$opts{shutdown}, |
||||
"shutdown-timeout=s" => \$opts{shutdown-timeout}, |
||||
"help" => \$opts{help} |
||||
); |
||||
|
||||
|
||||
# Set compression settings |
||||
if ($opts{compress} eq 'lzop'){ |
||||
$opts{compext} = ".lzo"; |
||||
$opts{compcmd} = "lzop -c"; |
||||
} |
||||
elsif ($opts{compress} eq 'bzip2'){ |
||||
$opts{compext} = ".bz2"; |
||||
$opts{compcmd} = "bzip2 -c"; |
||||
} |
||||
elsif ($opts{compress} eq 'pbzip2'){ |
||||
$opts{compext} = ".bz2"; |
||||
$opts{compcmd} = "pbzip2 -c"; |
||||
} |
||||
elsif ($opts{compress} eq 'xz'){ |
||||
$opts{compext} = ".xz"; |
||||
$opts{compcmd} = "xz -c"; |
||||
} |
||||
elsif ($opts{compress} eq 'lzip'){ |
||||
$opts{compext} = ".lz"; |
||||
$opts{compcmd} = "lzip -c"; |
||||
} |
||||
elsif ($opts{compress} eq 'plzip'){ |
||||
$opts{compext} = ".lz"; |
||||
$opts{compcmd} = "plzip -c"; |
||||
} |
||||
# Default is gzip |
||||
elsif (($opts{compress} eq 'gzip') || ($opts{compress} eq '')) { |
||||
$opts{compext} = ".gz"; |
||||
$opts{compcmd} = "gzip -c"; |
||||
} |
||||
else{ |
||||
$opts{compext} = ""; |
||||
$opts{compcmd} = "cat"; |
||||
} |
||||
|
||||
# Allow comma separated multi-argument |
||||
@vms = split(/,/,join(',',@vms)); |
||||
@excludes = split(/,/,join(',',@excludes)); |
||||
|
||||
# Backward compatible with --dump --cleanup --unlock |
||||
$opts{action} = 'dump' if ($opts{dump}); |
||||
$opts{action} = 'cleanup' if ($opts{cleanup}); |
||||
$opts{action} = 'unlock' if ($opts{unlock}); |
||||
|
||||
# Stop here if we have no vm |
||||
# Or the help flag is present |
||||
if ((!@vms) || ($opts{help})){ |
||||
usage(); |
||||
exit 1; |
||||
} |
||||
# Or state and shutdown flags are used together |
||||
if (($opts{state}) && ($opts{shutdown})){ |
||||
print "State and shutdown flags cannot be used together\n"; |
||||
exit 1; |
||||
} |
||||
|
||||
if (! -d $opts{backupdir} ){ |
||||
print "$opts{backupdir} is not a valid directory\n"; |
||||
exit 1; |
||||
} |
||||
|
||||
# Connect to libvirt |
||||
print "\n\nConnecting to libvirt daemon using $opts{connect} as URI\n" if ($opts{debug}); |
||||
our $libvirt = Sys::Virt->new( uri => $opts{connect} ) || |
||||
die "Error connecting to libvirt on URI: $opts{connect}"; |
||||
|
||||
|
||||
|
||||
print "\n" if ($opts{debug}); |
||||
|
||||
foreach our $vm (@vms){ |
||||
# Create a new object representing the VM |
||||
print "Checking $vm status\n\n" if ($opts{debug}); |
||||
our $dom = $libvirt->get_domain_by_name($vm) || |
||||
die "Error opening $vm object"; |
||||
our $backupdir = $opts{backupdir}.'/'.$vm; |
||||
if ($opts{action} eq 'cleanup'){ |
||||
print "Running cleanup routine for $vm\n\n" if ($opts{debug}); |
||||
run_cleanup(); |
||||
} |
||||
elsif ($opts{action} eq 'unlock'){ |
||||
print "Unlocking $vm\n\n" if ($opts{debug}); |
||||
unlock_vm(); |
||||
} |
||||
elsif ($opts{action} eq 'dump'){ |
||||
print "Running dump routine for $vm\n\n" if ($opts{debug}); |
||||
mkdir $backupdir || die $!; |
||||
mkdir $backupdir . '.meta' || die $!; |
||||
run_dump(); |
||||
} |
||||
elsif ($opts{action} eq 'chunkmount'){ |
||||
print "Running chunkmount routine for $vm\n\n" if ($opts{debug}); |
||||
mkdir $backupdir || die $!; |
||||
mkdir $backupdir . '.meta' || die $!; |
||||
run_chunkmount(); |
||||
} |
||||
else { |
||||
usage(); |
||||
exit 1; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
############################################################################ |
||||
############## FUNCTIONS #################### |
||||
############################################################################ |
||||
|
||||
|
||||
sub prepare_backup{ |
||||
# Create a new XML object |
||||
my $xml = new XML::Simple (); |
||||
my $data = $xml->XMLin( $dom->get_xml_description(), forcearray => ['disk'] ); |
||||
|
||||
# STop here if the lock file is present, another dump might be running |
||||
die "Another backup is running\n" if ( -e "$backupdir.meta/$vm.lock" ); |
||||
|
||||
# Lock VM: Create a lock file so only one dump process can run |
||||
lock_vm(); |
||||
|
||||
# Save the XML description |
||||
save_xml(); |
||||
|
||||
# Save the VM state if it's running and --state is present |
||||
# (else, just suspend the VM) |
||||
$opts{wasrunning} = 0 unless ($dom->is_active()); |
||||
|
||||
if ($opts{wasrunning}){ |
||||
if ($opts{state}){ |
||||
save_vm_state(); |
||||
} |
||||
elsif ($opts{shutdown}){ |
||||
shutdown_vm(); |
||||
} |
||||
else{ |
||||
suspend_vm(); |
||||
} |
||||
} |
||||
|
||||
# Create a list of disks used by the VM |
||||
foreach $disk (@{$data->{devices}->{disk}}){ |
||||
|
||||
my $source; |
||||
if ($disk->{type} eq 'block'){ |
||||
$source = $disk->{source}->{dev}; |
||||
} |
||||
elsif ($disk->{type} eq 'file'){ |
||||
$source = $disk->{source}->{file}; |
||||
} |
||||
else{ |
||||
print "\nSkiping $source for vm $vm as it's type is $disk->{type}: " . |
||||
" and only block and file are supported\n" if ($opts{debug}); |
||||
next; |
||||
} |
||||
my $target = $disk->{target}->{dev}; |
||||
|
||||
# Check if the current disk is not excluded |
||||
if (grep { $_ eq "$target" } @excludes){ |
||||
print "\nSkiping $source for vm $vm as it's matching one of the excludes: " . |
||||
join(",",@excludes)."\n\n" if ($opts{debug}); |
||||
next; |
||||
} |
||||
|
||||
# If the device is a disk (and not a cdrom) and the source dev exists |
||||
if (($disk->{device} eq 'disk') && (-e $source)){ |
||||
|
||||
print "\nAnalysing disk $source connected on $vm as $target\n\n" if ($opts{debug}); |
||||
|
||||
# If it's a block device |
||||
if ($disk->{type} eq 'block'){ |
||||
|
||||
my $time = "_".time(); |
||||
# Try to snapshot the source if snapshot is enabled |
||||
if ( ($opts{snapshot}) && (create_snapshot($source,$time)) ){ |
||||
print "$source seems to be a valid logical volume (LVM), a snapshot has been taken as " . |
||||
$source . $time ."\n" if ($opts{debug}); |
||||
$source = $source.$time; |
||||
push (@disks, {source => $source, target => $target, type => 'snapshot'}); |
||||
} |
||||
# Snapshot failed, or disabled: disabling live backups |
||||
else{ |
||||
if ($opts{snapshot}){ |
||||
print "Snapshoting $source has failed (not managed by LVM, or already a snapshot ?)" . |
||||
", live backup will be disabled\n" if ($opts{debug}) ; |
||||
} |
||||
else{ |
||||
print "Not using LVM snapshots, live backups will be disabled\n" if ($opts{debug}); |
||||
} |
||||
$opts{livebackup} = 0; |
||||
push (@disks, {source => $source, target => $target, type => 'block'}); |
||||
} |
||||
} |
||||
elsif ($disk->{type} eq 'file'){ |
||||
$opts{livebackup} = 0; |
||||
push (@disks, {source => $source, target => $target, type => 'file'}); |
||||
} |
||||
print "Adding $source to the list of disks to be backed up\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
# Summarize the list of disk to be dumped |
||||
if ($opts{debug}){ |
||||
if ($opts{action} eq 'dump'){ |
||||
print "\n\nThe following disks will be dumped:\n\n"; |
||||
foreach $disk (@disks){ |
||||
print "Source: $disk->{source}\tDest: $backupdir/$vm" . '_' . $disk->{target} . |
||||
".img$opts{compext}\n"; |
||||
} |
||||
} |
||||
elsif($opts{action} eq 'chunkmount'){ |
||||
print "\n\nThe following disks will be mounted as chunks:\n\n"; |
||||
foreach $disk (@disks){ |
||||
print "Source: $disk->{source}\tDest: $backupdir/$vm" . '_' . $disk->{target}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
# If livebackup is possible (every block devices can be snapshoted) |
||||
# We can restore the VM now, in order to minimize the downtime |
||||
if ($opts{livebackup}){ |
||||
print "\nWe can run a live backup\n" if ($opts{debug}); |
||||
if ($opts{wasrunning}){ |
||||
if ($opts{state}){ |
||||
restore_vm(); |
||||
} |
||||
elsif ($opts{shutdown}){ |
||||
start_vm(); |
||||
} |
||||
else{ |
||||
resume_vm(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
sub run_dump{ |
||||
|
||||
# Pause VM, dump state, take snapshots etc.. |
||||
prepare_backup(); |
||||
|
||||
# Now, it's time to actually dump the disks |
||||
foreach $disk (@disks){ |
||||
|
||||
my $source = $disk->{source}; |
||||
my $dest = "$backupdir/$vm" . '_' . $disk->{target} . ".img$opts{compext}"; |
||||
|
||||
print "\nStarting dump of $source to $dest\n\n" if ($opts{debug}); |
||||
my $ddcmd = "$opts{ionice} dd if=$source bs=$opts{blocksize} | $opts{nice} $opts{compcmd} > $dest 2>/dev/null"; |
||||
unless( system("$ddcmd") == 0 ){ |
||||
die "Couldn't dump the block device/file $source to $dest\n"; |
||||
} |
||||
# Remove the snapshot if the current dumped disk is a snapshot |
||||
destroy_snapshot($source) if ($disk->{type} eq 'snapshot'); |
||||
} |
||||
|
||||
# If the VM was running before the dump, restore (or resume) it |
||||
if ($opts{wasrunning}){ |
||||
if ($opts{state}){ |
||||
restore_vm(); |
||||
} |
||||
else{ |
||||
resume_vm(); |
||||
} |
||||
} |
||||
# And remove the lock file, unless the --keep-lock flag is present |
||||
unlock_vm() unless ($opts{keeplock}); |
||||
} |
||||
|
||||
sub run_chunkmount{ |
||||
# Pause VM, dump state, take snapshots etc.. |
||||
prepare_backup(); |
||||
|
||||
# Now, lets mount guest images with chunkfs |
||||
foreach $disk (@disks){ |
||||
|
||||
my $source = $disk->{source}; |
||||
my $dest = "$backupdir/$vm" . '_' . $disk->{target}; |
||||
mkdir $dest || die $!; |
||||
print "\nMounting $source on $dest with chunkfs\n\n" if ($opts{debug}); |
||||
my $cmd = "$opts{ionice} $opts{chunkfs} -o fsname=chunkfs-$vm $opts{blocksize} $source $dest 2>/dev/null"; |
||||
unless( system("$cmd") == 0 ){ |
||||
die "Couldn't mount $source on $dest\n"; |
||||
} |
||||
} |
||||
} |
||||
|
||||
# Remove the dumps |
||||
sub run_cleanup{ |
||||
print "\nRemoving backup files\n" if ($opts{debug}); |
||||
my $cnt = 0; |
||||
my $meta = 0; |
||||
my $snap = 0; |
||||
|
||||
# If a state file is present, restore the VM |
||||
if (-e "$backupdir/$vm.state"){ |
||||
restore_vm(); |
||||
} |
||||
# Else, trys to resume it |
||||
else{ |
||||
resume_vm(); |
||||
} |
||||
|
||||
if (open MOUNTS, "</proc/mounts"){ |
||||
foreach (<MOUNTS>){ |
||||
my @info = split(/\s+/, $_); |
||||
next unless ($info[0] eq "chunkfs-$vm"); |
||||
print "Found chunkfs mount point: $info[1]\n" if ($opts{debug}); |
||||
my $mp = $info[1]; |
||||
print "Unmounting chunkfs mount point $mp\n\n" if ($opts{debug}); |
||||
die "Couldn't unmount $mp\n" unless ( |
||||
system("/bin/umount $mp 2>/dev/null") == 0 |
||||
); |
||||
rmdir $mp || die $!; |
||||
} |
||||
close MOUNTS; |
||||
} |
||||
|
||||
$cnt = unlink <$backupdir/*>; |
||||
if (open SNAPLIST, "<$backupdir.meta/snapshots"){ |
||||
foreach (<SNAPLIST>){ |
||||
# Destroy snapshot listed here is they exists |
||||
# and only if the end with _ and 10 digits |
||||
chomp; |
||||
if ((-e $_) && ($_ =~ m/_\d{10}$/)){ |
||||
print "Found $_ in snapshot list file, will try to remove it\n" if ($opts{debug}); |
||||
destroy_snapshot($_); |
||||
$snap++; |
||||
} |
||||
} |
||||
close SNAPLIST; |
||||
} |
||||
$meta = unlink <$backupdir.meta/*>; |
||||
rmdir "$backupdir/"; |
||||
rmdir "$backupdir.meta"; |
||||
print "$cnt file(s) removed\n$snap LVM snapshots removed\n$meta metadata files removed\n\n" if $opts{debug}; |
||||
} |
||||
|
||||
|
||||
sub usage{ |
||||
print "usage:\n$0 --action=[dump|cleanup|chunkmount|unlock] --vm=vm1[,vm2,vm3] [--debug] [--exclude=hda,hdb] [--compress] ". |
||||
"[--state] [--shutdown] [--shutdown-timeout] [--no-snapshot] [--snapsize=<size>] [--backupdir=/path/to/dir] [--connect=<URI>] ". |
||||
"[--keep-lock] [--bs=<block size>]\n" . |
||||
"\n\n" . |
||||
"\t--action: What action the script will run. Valid actions are\n\n" . |
||||
"\t\t- dump: Run the dump routine (dump disk image to temp dir, pausing the VM if needed). It's the default action\n" . |
||||
"\t\t- cleanup: Run the cleanup routine, cleaning up the backup dir\n" . |
||||
"\t\t- chunkmount: Mount each device as a chunkfs mount point directly in the backup dir\n" . |
||||
"\t\t- unlock: just remove the lock file, but don't cleanup the backup dir\n\n" . |
||||
"\t--vm=name: The VM you want to work on (as known by libvirt). You can backup several VMs in one shot " . |
||||
"if you separate them with comma, or with multiple --vm argument. You have to use the name of the domain, ". |
||||
"ID and UUID are not supported at the moment\n\n" . |
||||
"\n\nOther options:\n\n" . |
||||
"\t--state: Cleaner way to take backups. If this flag is present, the script will save the current state of " . |
||||
"the VM (if running) instead of just suspending it. With this you should be able to restore the VM at " . |
||||
"the exact state it was when the backup started. The reason this flag is optional is that some guests " . |
||||
"crashes after the restoration, especially when using the kvm-clock. Test this functionnality with" . |
||||
"your environnement before using this flag on production. This flag is mutual exclusive with --shutdown\n\n" . |
||||
"\t--no-snapshot: Do not attempt to use LVM snapshots. If not present, the script will try to take a snapshot " . |
||||
"of each disk of type 'block'. If all disk can be snapshoted, the VM is resumed, or restored (depending " . |
||||
"on the --state flag) immediatly after the snapshots have been taken, resulting in almost no downtime. " . |
||||
"This is called a \"live backup\" in this script" . |
||||
"If at least one disk cannot be snapshoted, the VM is suspended (or stoped) for the time the disks are " . |
||||
"dumped in the backup dir. That's why you should use a fast support for the backup dir (fast disks, RAID0 " . |
||||
"or RAID10)\n\n" . |
||||
"\t--snapsize=<snapsize>: The amount of space to use for snapshots. Use the same format as -L option of lvcreate. " . |
||||
"eg: --snapsize=15G. Default is 5G\n\n" . |
||||
"\t--compress[=[gzip|bzip2|pbzip2|lzop|xz|lzip|plzip]]: On the fly compress the disks images during the dump. If you " . |
||||
"don't specify a compression algo, gzip will be used.\n\n" . |
||||
"\t--exclude=hda,hdb: Prevent the disks listed from being dumped. The names are from the VM perspective, as " . |
||||
"configured in livirt as the target element. It can be usefull for example if you want to dump the system " . |
||||
"disk of a VM, but not the data one which can be backed up separatly, at the files level.\n\n" . |
||||
"\t--backupdir=/path/to/backup: Use an alternate backup dir. The directory must exists and be writable. " . |
||||
"The default is /var/lib/libvirt/backup\n\n" . |
||||
"\t--connect=<URI>: URI to connect to libvirt daemon (to suspend, resume, save, restore VM etc...). " . |
||||
"The default is qemu:///system.\n\n" . |
||||
"\t--keep-lock: Let the lock file present. This prevent another " . |
||||
"dump to run while an third party backup software (BackupPC for example) saves the dumped files.\n\n" . |
||||
"\t--shutdown: Shutdown the vm instead of suspending it. This uses ACPI to send the shutdown signal. " . |
||||
"You should make sure your guest react to ACPI signals. This flag is mutual exclusive with --state\n\n" . |
||||
"\t--shutdown-timeout=<seconds>: How long to wait, in seconds, for the vm to shutdown. If the VM isn't stopped " . |
||||
"after that amount of time (in seconds), the backup will abort. The default timeout is 300 seconds"; |
||||
} |
||||
|
||||
# Save a running VM, if it's running |
||||
sub save_vm_state{ |
||||
if ($dom->is_active()){ |
||||
print "$vm is running, saving state....\n" if ($opts{debug}); |
||||
$dom->save("$backupdir/$vm.state"); |
||||
print "$vm state saved as $backupdir/$vm.state\n" if ($opts{debug}); |
||||
} |
||||
else{ |
||||
print "$vm is not running, nothing to do\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
# Restore the state of a VM |
||||
sub restore_vm{ |
||||
if (! $dom->is_active()){ |
||||
if (-e "$backupdir/$vm.state"){ |
||||
print "\nTrying to restore $vm from $backupdir/$vm.state\n" if ($opts{debug}); |
||||
$libvirt->restore_domain("$backupdir/$vm.state"); |
||||
print "Waiting for restoration to complete\n" if ($opts{debug}); |
||||
my $i = 0; |
||||
while ((!$dom->is_active()) && ($i < 120)){ |
||||
sleep(5); |
||||
$i = $i+5; |
||||
} |
||||
print "Timeout while trying to restore $vm, aborting\n" |
||||
if (($i > 120) && ($opts{debug})); |
||||
} |
||||
else{ |
||||
print "\nRestoration impossible, $backupdir/$vm.state is missing\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
else{ |
||||
print "\nCannot start domain restoration, $vm is running (maybe already restored after a live backup ?)\n" |
||||
if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
# Suspend a VM |
||||
sub suspend_vm(){ |
||||
if ($dom->is_active()){ |
||||
print "$vm is running, suspending\n" if ($opts{debug}); |
||||
$dom->suspend(); |
||||
print "$vm now suspended\n" if ($opts{debug}); |
||||
} |
||||
else{ |
||||
print "$vm is not running, nothing to do\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
# Resume a VM if it's paused |
||||
sub resume_vm(){ |
||||
if ($dom->get_info->{state} == Sys::Virt::Domain::STATE_PAUSED){ |
||||
print "$vm is suspended, resuming\n" if ($opts{debug}); |
||||
$dom->resume(); |
||||
print "$vm now resumed\n" if ($opts{debug}); |
||||
} |
||||
else{ |
||||
print "$vm is not suspended, nothing to do\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
|
||||
# Shutdown a VM via ACPI |
||||
sub shutdown_vm(){ |
||||
if ($dom->is_active()){ |
||||
print "$vm is running, shutting down\n" if ($opts{debug}); |
||||
$dom->shutdown(); |
||||
my $shutdown_counter = 0; |
||||
# Wait $opts{shutdown-timeout} seconds for vm to shutdown |
||||
while ($dom->get_info->{state} != Sys::Virt::Domain::STATE_SHUTOFF){ |
||||
if ($shutdown_counter >= $opts{shutdown-timeout}){ |
||||
die "Waited $opts{shutdown_time} seconds for $vm to shutdown. Shutdown Failed\n"; |
||||
} |
||||
$shutdown_counter++; |
||||
sleep(1); |
||||
} |
||||
} |
||||
else{ |
||||
print "$vm is not running, nothing to do\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
sub start_vm(){ |
||||
if ($dom->get_info->{state} == Sys::Virt::Domain::STATE_SHUTOFF){ |
||||
print "$vm is shutoff, restarting\n" if ($opts{debug}); |
||||
$dom->create(); |
||||
print "$vm started\n" if ($opts{debug}); |
||||
} |
||||
else{ |
||||
print "$vm is not in a shutdown state, nothing to do\n" if ($opts{debug}); |
||||
} |
||||
} |
||||
|
||||
# Dump the domain description as XML |
||||
sub save_xml{ |
||||
print "\nSaving XML description for $vm to $backupdir/$vm.xml\n" if ($opts{debug}); |
||||
open(XML, ">$backupdir/$vm" . ".xml") || die $!; |
||||
print XML $dom->get_xml_description(); |
||||
close XML; |
||||
} |
||||
|
||||
# Create an LVM snapshot |
||||
# Pass the original logical volume and the suffix |
||||
# to be added to the snapshot name as arguments |
||||
sub create_snapshot{ |
||||
my ($blk,$suffix) = @_; |
||||
my $ret = 0; |
||||
print "Running: $opts{lvcreate} -p r -s -n " . $blk . $suffix . |
||||
" -L $opts{snapsize} $blk > /dev/null 2>&1\n" if $opts{debug}; |
||||
if ( system("$opts{lvcreate} -s -n " . $blk . $suffix . |
||||
" -L $opts{snapsize} $blk > /dev/null 2>&1") == 0 ) { |
||||
$ret = 1; |
||||
open SNAPLIST, ">>$backupdir.meta/snapshots" or die "Error, couldn't open snapshot list file\n"; |
||||
print SNAPLIST $blk.$suffix ."\n"; |
||||
close SNAPLIST; |
||||
} |
||||
return $ret; |
||||
} |
||||
|
||||
# Remove an LVM snapshot |
||||
sub destroy_snapshot{ |
||||
my $ret = 0; |
||||
my ($snap) = @_; |
||||
print "Removing snapshot $snap\n" if $opts{debug}; |
||||
if (system ("$opts{lvremove} -f $snap > /dev/null 2>&1") == 0 ){ |
||||
$ret = 1; |
||||
} |
||||
return $ret; |
||||
} |
||||
|
||||
# Lock a VM backup dir |
||||
# Just creates an empty lock file |
||||
sub lock_vm{ |
||||
print "Locking $vm\n" if $opts{debug}; |
||||
open ( LOCK, ">$backupdir.meta/$vm.lock" ) || die $!; |
||||
print LOCK ""; |
||||
close LOCK; |
||||
} |
||||
|
||||
# Unlock the VM backup dir |
||||
# Just removes the lock file |
||||
sub unlock_vm{ |
||||
print "Removing lock file for $vm\n\n" if $opts{debug}; |
||||
unlink <$backupdir.meta/$vm.lock>; |
||||
} |
||||
|
@ -0,0 +1,72 @@ |
||||
|
||||
Summary: backup script for libvirt managed VM |
||||
Name: virt-backup |
||||
Version: 0.1.0 |
||||
Release: 1.beta0 |
||||
Source0: %{name}-%{version}.tar.gz |
||||
BuildArch: noarch |
||||
|
||||
License: GPL |
||||
Group: Virtualization |
||||
BuildRoot: %{_tmppath}/%{name}-buildroot |
||||
Prefix: %{_prefix} |
||||
Requires: lvm2 |
||||
Requires: util-linux |
||||
Requires: lzop, bzip2, pbzip2, gzip, xz |
||||
Requires: perl(Sys::Virt) => 0.2.3 |
||||
Requires: perl(XML::Simple) |
||||
Requires: fuse-chunkfs |
||||
|
||||
Requires(pre): shadow-utils |
||||
Requires(preun): initscripts, chkconfig |
||||
Requires(postun): initscripts |
||||
Requires(post): initscripts, chkconfig |
||||
|
||||
AutoReq: no |
||||
|
||||
%description |
||||
This package contains utilities for virtualization stack |
||||
on RHEL & ci. It provides for example SELinux policy, hook |
||||
scripts to set permissions on ressources files |
||||
etc... |
||||
|
||||
%prep |
||||
%setup -q |
||||
|
||||
%build |
||||
|
||||
%install |
||||
|
||||
%{__rm} -rf $RPM_BUILD_ROOT |
||||
|
||||
# Install backup script |
||||
%{__mkdir_p} $RPM_BUILD_ROOT%{_bindir}/ |
||||
%{__install} -m 0755 virt-backup $RPM_BUILD_ROOT%{_bindir}/ |
||||
|
||||
# Create backup dir |
||||
%{__mkdir_p} $RPM_BUILD_ROOT%{_localstatedir}/lib/libvirt/backup |
||||
|
||||
%clean |
||||
%{__rm} -rf $RPM_BUILD_ROOT |
||||
|
||||
%pre |
||||
getent group kvm >/dev/null || groupadd -g 36 -r kvm |
||||
getent group qemu >/dev/null || groupadd -g 107 -r qemu |
||||
getent passwd qemu >/dev/null || \ |
||||
useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ |
||||
-c "qemu user" qemu |
||||
|
||||
%preun |
||||
|
||||
%post |
||||
|
||||
%files |
||||
%defattr(-,root,root,-) |
||||
%doc README |
||||
%{_bindir}/* |
||||
%dir %attr(0770, qemu, kvm) %{_localstatedir}/lib/libvirt/backup |
||||
|
||||
%changelog |
||||
* Tue May 23 2012 Daniel B. <daniel@firewall-services.com> - 0.1.0-1 |
||||
- Move virt-backup to it's own RPM (and GIT repo) |
||||
|
Loading…
Reference in new issue