Support a two nodes cluster situation, where images are files on the host, backed by an LVM volume (and also support the case where GlusterFS is between the LVM and the guest images)

This commit is contained in:
Daniel Berteaud 2013-07-24 12:35:00 +02:00
parent f52fe007ab
commit ef0c33ae97

View File

@ -26,6 +26,7 @@
use XML::Simple; use XML::Simple;
use Sys::Virt; use Sys::Virt;
use Getopt::Long; use Getopt::Long;
use File::Copy;
# Set umask # Set umask
umask(022); umask(022);
@ -54,13 +55,15 @@ $opts{keeplock} = 0;
# Should we try to create LVM snapshots during the dump ? # Should we try to create LVM snapshots during the dump ?
$opts{snapshot} = 1; $opts{snapshot} = 1;
# Libvirt URI to connect to # Libvirt URI to connect to
$opts{connect} = "qemu:///system"; @connect = ();
# Compression used with the dump action (the compression is done on the fly) # Compression used with the dump action (the compression is done on the fly)
$opts{compress} = 'none'; $opts{compress} = 'none';
# lvcreate path # lvcreate path
$opts{lvcreate} = '/sbin/lvcreate -c 512'; $opts{lvcreate} = '/sbin/lvcreate -c 512';
# lvremove path # lvremove path
$opts{lvremove} = '/sbin/lvremove'; $opts{lvremove} = '/sbin/lvremove';
# Override path to the LVM backend
$opts{lvm} = '';
# chunkfs path # chunkfs path
$opts{chunkfs} = '/usr/bin/chunkfs'; $opts{chunkfs} = '/usr/bin/chunkfs';
# Size of chunks to use with chunkfs or or blocks with dd in bytes (default to 256kB) # Size of chunks to use with chunkfs or or blocks with dd in bytes (default to 256kB)
@ -84,12 +87,13 @@ GetOptions(
"state" => \$opts{state}, "state" => \$opts{state},
"snapsize=s" => \$opts{snapsize}, "snapsize=s" => \$opts{snapsize},
"backupdir=s" => \$opts{backupdir}, "backupdir=s" => \$opts{backupdir},
"lvm=s" => \$opts{lvm},
"vm=s" => \@vms, "vm=s" => \@vms,
"action=s" => \$opts{action}, "action=s" => \$opts{action},
"cleanup" => \$opts{cleanup}, "cleanup" => \$opts{cleanup},
"dump" => \$opts{dump}, "dump" => \$opts{dump},
"unlock" => \$opts{unlock}, "unlock" => \$opts{unlock},
"connect=s" => \$opts{connect}, "connect=s" => \@connect,
"snapshot!" => \$opts{snapshot}, "snapshot!" => \$opts{snapshot},
"compress:s" => \$opts{compress}, "compress:s" => \$opts{compress},
"exclude=s" => \@excludes, "exclude=s" => \@excludes,
@ -138,6 +142,10 @@ else{
# Allow comma separated multi-argument # Allow comma separated multi-argument
@vms = split(/,/,join(',',@vms)); @vms = split(/,/,join(',',@vms));
@excludes = split(/,/,join(',',@excludes)); @excludes = split(/,/,join(',',@excludes));
@connect = split(/,/,join(',',@connect));
# Define a default libvirt URI
$connect[0] = "qemu:///system" unless (defined $connect[0]);
# Backward compatible with --dump --cleanup --unlock # Backward compatible with --dump --cleanup --unlock
$opts{action} = 'dump' if ($opts{dump}); $opts{action} = 'dump' if ($opts{dump});
@ -163,18 +171,40 @@ if (! -d $opts{backupdir} ){
} }
# Connect to libvirt # Connect to libvirt
print "\n\nConnecting to libvirt daemon using $opts{connect} as URI\n" if ($opts{debug}); print "\n\nConnecting to libvirt daemon using $connect[0] as URI\n" if ($opts{debug});
our $libvirt = Sys::Virt->new( uri => $opts{connect} ) || $libvirt1 = Sys::Virt->new( uri => $connect[0] ) ||
die "Error connecting to libvirt on URI: $opts{connect}"; die "Error connecting to libvirt on URI: $connect[0]";
if (defined $connect[1]){
$libvirt2 = Sys::Virt->new( uri => $connect[1] ) ||
die "Error connecting to libvirt on URI: $connect[1]";
}
our $libvirt = $libvirt1;
print "\n" if ($opts{debug}); print "\n" if ($opts{debug});
foreach our $vm (@vms){ foreach our $vm (@vms){
# Create a new object representing the VM # Create a new object representing the VM
print "Checking $vm status\n\n" if ($opts{debug}); print "Checking $vm status\n\n" if ($opts{debug});
our $dom = $libvirt->get_domain_by_name($vm) || our $dom = $libvirt1->get_domain_by_name($vm) ||
die "Error opening $vm object"; die "Error opening $vm object";
# If we've passed two connect URI, and our VM is not
# running on the first one, check on the second one
if (!$dom->is_active && defined $connect[1]){
$dom = $libvirt2->get_domain_by_name($vm) ||
die "Error opening $vm object";
if ($dom->is_active()){
$libvirt = $libvirt2;
}
else{
$dom = $libvirt1->get_domain_by_name($vm);
}
}
our $backupdir = $opts{backupdir}.'/'.$vm; our $backupdir = $opts{backupdir}.'/'.$vm;
our $time = "_".time();
if ($opts{action} eq 'cleanup'){ if ($opts{action} eq 'cleanup'){
print "Running cleanup routine for $vm\n\n" if ($opts{debug}); print "Running cleanup routine for $vm\n\n" if ($opts{debug});
run_cleanup(); run_cleanup();
@ -187,12 +217,14 @@ foreach our $vm (@vms){
print "Running dump routine for $vm\n\n" if ($opts{debug}); print "Running dump routine for $vm\n\n" if ($opts{debug});
mkdir $backupdir || die $!; mkdir $backupdir || die $!;
mkdir $backupdir . '.meta' || die $!; mkdir $backupdir . '.meta' || die $!;
mkdir $backupdir . '.mount' || die $!;
run_dump(); run_dump();
} }
elsif ($opts{action} eq 'chunkmount'){ elsif ($opts{action} eq 'chunkmount'){
print "Running chunkmount routine for $vm\n\n" if ($opts{debug}); print "Running chunkmount routine for $vm\n\n" if ($opts{debug});
mkdir $backupdir || die $!; mkdir $backupdir || die $!;
mkdir $backupdir . '.meta' || die $!; mkdir $backupdir . '.meta' || die $!;
mkdir $backupdir . '.mount' || die $!;
run_chunkmount(); run_chunkmount();
} }
else { else {
@ -270,7 +302,6 @@ sub prepare_backup{
# If it's a block device # If it's a block device
if ($disk->{type} eq 'block'){ if ($disk->{type} eq 'block'){
my $time = "_".time();
# Try to snapshot the source if snapshot is enabled # Try to snapshot the source if snapshot is enabled
if ( ($opts{snapshot}) && (create_snapshot($source,$time)) ){ if ( ($opts{snapshot}) && (create_snapshot($source,$time)) ){
print "$source seems to be a valid logical volume (LVM), a snapshot has been taken as " . print "$source seems to be a valid logical volume (LVM), a snapshot has been taken as " .
@ -292,9 +323,43 @@ sub prepare_backup{
} }
} }
elsif ($disk->{type} eq 'file'){ elsif ($disk->{type} eq 'file'){
# Try to find the mount point, and the backing device
my @df = `df -P $source`;
my ($dev,undef,undef,undef,undef,$mount) = split /\s+/, $df[1];
# The backing device can be detected, but can also be overwritten with --lvm=/dev/data/vm
# This can be usefull for example when you use GlusterFS. Df will return something like
# localhost:/vmstore as the device, but this GlusterFS volume might be backed by an LVM
# volume, in which case, you can pass it as an argument to the script
my $lvm = ($opts{lvm} ne '' && -e "$opts{lvm}") ? $opts{lvm} : $dev;
# Try to snapshot this device
if ( $opts{snapshot} ){
# Maybe the LVM is already snapshoted and mounted for a previous disk ?
my $is_mounted = 0;
if (open MOUNT, "<$backupdir.meta/mount"){
while (<MOUNT>){
$is_mounted = 1 if ($_ eq $lvm);
}
close MOUNT;
}
if (!$is_mounted && create_snapshot($lvm,$time)){
print "$lvm seems to be a valid logical volume (LVM), a snapshot has been taken as " .
$lvm . $time ."\n" if ($opts{debug});
my $snap = $lvm.$time;
# -o nouuid is needed if XFS is used
system("/bin/mount -o nouuid $snap $backupdir.mount");
open MOUNT, ">$backupdir.meta/mount";
print MOUNT $lvm;
close MOUNT;
}
my $file = $source;
$file =~ s|$mount|$backupdir.mount|;
push (@disks, {source => $file, target => $target, type => 'file'});
}
else {
$opts{livebackup} = 0; $opts{livebackup} = 0;
push (@disks, {source => $source, target => $target, type => 'file'}); push (@disks, {source => $source, target => $target, type => 'file'});
} }
}
print "Adding $source to the list of disks to be backed up\n" if ($opts{debug}); print "Adding $source to the list of disks to be backed up\n" if ($opts{debug});
} }
} }
@ -405,12 +470,26 @@ sub run_cleanup{
} }
if (open MOUNTS, "</proc/mounts"){ if (open MOUNTS, "</proc/mounts"){
foreach (<MOUNTS>){ my @mounts = <MOUNTS>;
# We first need to umount chunkfs mount points
foreach (@mounts){
my @info = split(/\s+/, $_); my @info = split(/\s+/, $_);
next unless ($info[0] eq "chunkfs-$vm"); next unless ($info[0] eq "chunkfs-$vm");
print "Found chunkfs mount point: $info[1]\n" if ($opts{debug}); print "Found chunkfs mount point: $info[1]\n" if ($opts{debug});
my $mp = $info[1]; my $mp = $info[1];
print "Unmounting chunkfs mount point $mp\n\n" if ($opts{debug}); print "Unmounting $mp\n\n" if ($opts{debug});
die "Couldn't unmount $mp\n" unless (
system("/bin/umount $mp 2>/dev/null") == 0
);
rmdir $mp || die $!;
}
# Now, standard filesystems
foreach (@mounts){
my @info = split(/\s+/, $_);
next unless ($info[1] eq "$backupdir.mount");
print "Found temporary mount point: $info[1]\n" if ($opts{debug});
my $mp = $info[1];
print "Unmounting $mp\n\n" if ($opts{debug});
die "Couldn't unmount $mp\n" unless ( die "Couldn't unmount $mp\n" unless (
system("/bin/umount $mp 2>/dev/null") == 0 system("/bin/umount $mp 2>/dev/null") == 0
); );
@ -441,6 +520,7 @@ sub run_cleanup{
$meta = unlink <$backupdir.meta/*>; $meta = unlink <$backupdir.meta/*>;
rmdir "$backupdir/"; rmdir "$backupdir/";
rmdir "$backupdir.meta"; rmdir "$backupdir.meta";
rmdir "$backupdir.mount";
print "$cnt file(s) removed\n$snap LVM snapshots removed\n$meta metadata files removed\n\n" if $opts{debug}; print "$cnt file(s) removed\n$snap LVM snapshots removed\n$meta metadata files removed\n\n" if $opts{debug};
} }
@ -495,7 +575,20 @@ sub usage{
sub save_vm_state{ sub save_vm_state{
if ($dom->is_active()){ if ($dom->is_active()){
print "$vm is running, saving state....\n" if ($opts{debug}); print "$vm is running, saving state....\n" if ($opts{debug});
# if connect[1] is defined, you've passed several connection URI
# This means that you're running a dual hypervisor cluster
# And depending on the one running the current VM
# $backupdir might not be available
# whereas /var/lib/libvirt/qemu/save/ might
# if you've mounted here a shared file system
# (NFS, GlusterFS, GFS2, OCFS etc...)
if (defined $connect[1]){
$dom->managed_save();
move "/var/lib/libvirt/qemu/save/$vm.save", "$backupdir/$vm.state";
}
else{
$dom->save("$backupdir/$vm.state"); $dom->save("$backupdir/$vm.state");
}
print "$vm state saved as $backupdir/$vm.state\n" if ($opts{debug}); print "$vm state saved as $backupdir/$vm.state\n" if ($opts{debug});
} }
else{ else{
@ -507,6 +600,18 @@ sub save_vm_state{
sub restore_vm{ sub restore_vm{
if (! $dom->is_active()){ if (! $dom->is_active()){
if (-e "$backupdir/$vm.state"){ if (-e "$backupdir/$vm.state"){
# if connect[1] is defined, you've passed several connection URI
# This means that you're running a dual hypervisor cluster
# And depending on the one running the current VM
# $backupdir might not be available
# whereas /var/lib/libvirt/qemu/save/ might
# if you've mounted here a shared file system
# (NFS, GlusterFS, GFS2, OCFS etc...)
if (defined $connect[1]){
copy "$backupdir/$vm.state", "/var/lib/libvirt/qemu/save/$vm.save";
start_vm();
}
else{
print "\nTrying to restore $vm from $backupdir/$vm.state\n" if ($opts{debug}); print "\nTrying to restore $vm from $backupdir/$vm.state\n" if ($opts{debug});
$libvirt->restore_domain("$backupdir/$vm.state"); $libvirt->restore_domain("$backupdir/$vm.state");
print "Waiting for restoration to complete\n" if ($opts{debug}); print "Waiting for restoration to complete\n" if ($opts{debug});
@ -518,6 +623,7 @@ sub restore_vm{
print "Timeout while trying to restore $vm, aborting\n" print "Timeout while trying to restore $vm, aborting\n"
if (($i > 120) && ($opts{debug})); if (($i > 120) && ($opts{debug}));
} }
}
else{ else{
print "\nRestoration impossible, $backupdir/$vm.state is missing\n" if ($opts{debug}); print "\nRestoration impossible, $backupdir/$vm.state is missing\n" if ($opts{debug});
} }
@ -597,9 +703,9 @@ sub save_xml{
sub create_snapshot{ sub create_snapshot{
my ($blk,$suffix) = @_; my ($blk,$suffix) = @_;
my $ret = 0; my $ret = 0;
print "Running: $opts{lvcreate} -p r -s -n " . $blk . $suffix . print "Running: $opts{lvcreate} -s -n " . $blk . $suffix .
" -L $opts{snapsize} $blk > /dev/null 2>&1 < /dev/null\n" if $opts{debug}; " -L $opts{snapsize} $blk > /dev/null 2>&1 < /dev/null\n" if $opts{debug};
if ( system("$opts{lvcreate} -p r -s -n " . $blk . $suffix . if ( system("$opts{lvcreate} -s -n " . $blk . $suffix .
" -L $opts{snapsize} $blk > /dev/null 2>&1 < /dev/null") == 0 ) { " -L $opts{snapsize} $blk > /dev/null 2>&1 < /dev/null") == 0 ) {
$ret = 1; $ret = 1;
open SNAPLIST, ">>$backupdir.meta/snapshots" or die "Error, couldn't open snapshot list file\n"; open SNAPLIST, ">>$backupdir.meta/snapshots" or die "Error, couldn't open snapshot list file\n";