X-Git-Url: https://oss.titaniummirror.com/gitweb?a=blobdiff_plain;f=BackupPC_ovz;h=014e0827edcb36da2d48df2b8b16f11d4a399c19;hb=bf4484397a4859f7a246c675aa857441a6598e3c;hp=7106749f84d61f152bf5bb7495233f60028366cb;hpb=fe07358077e767579512761446b889a8159003ed;p=ovzbpc.git diff --git a/BackupPC_ovz b/BackupPC_ovz index 7106749..014e082 100755 --- a/BackupPC_ovz +++ b/BackupPC_ovz @@ -1,12 +1,12 @@ #!/usr/bin/perl # # BackupPC_ovz +# Version: __appVersion__ # # OpenVZ integration for BackupPC allowing the latter to backup OpenVZ VE's # with ovz awareness to improve backup and restore efficiency and features. - +# # FIXME: signal handling to clean up mount point and snapshot on termination -# FIXME: saveConfigs and restoreConfigs aren't being used yet use strict; use Socket; @@ -16,12 +16,26 @@ use File::Path; use Proc::PID::File; # Various constants -my @HNS = ('pe18001.titaniummirror.com', 'pe18002.titaniummirror.com'); my @script_ext = qw(start stop mount umount); my @velist = (); my $pidfile = "/tmp/".basename($0).".pid"; my $vzsnap = 'vzsnap'; # Mount point and lv names. Mount is relative to /. -my $snapsize = '1g'; +my $snapsize = '25g'; +my $hnlistFile = "/etc/backuppc/".basename($0).".hnlist"; +my $velistFile = $ENV{HOME}."/log/".basename($0).".velist"; + +# Uncomment one of the following +my $dbgf; +open $dbgf, ">>/tmp/BackupPC_ovz.debug" || die "Cannot open debug file"; +#open $dbgf, ">>/dev/null" || die "Cannot open debug file"; + +sub mydie($) +{ + my ($text) = @_; + + print $dbgf ": die $text\n"; + die $text; +} sub cmdExecOrEval { @@ -33,7 +47,8 @@ sub cmdExecOrEval print(STDERR "Perl code fragment for exec shouldn't return!!\n"); exit(1); } else { - $cmd = [split(/\s+/, $cmd)] if ( ref($cmd) ne "ARRAY" ); + $cmd = [split(/\s+/, $cmd)] if ( ref($cmd) ne "ARRAY" ); + print $dbgf ": execing command $cmd\n"; alarm(0); $cmd = [map { m/(.*)/ } @$cmd]; # untaint # @@ -54,8 +69,10 @@ sub cmdSystemOrEval $? = 0; $cmd = join(" ", $cmd) if ( ref($cmd) eq "ARRAY" ); if ( (ref($cmd) eq "ARRAY" ? $cmd->[0] : $cmd) =~ /^\&/ ) { + print $dbgf ": evaluating command $cmd\n"; eval($cmd); } else { + print $dbgf ": running command $cmd\n"; system($cmd); } } @@ -67,11 +84,19 @@ sub refreshConfig() { # Write the VEs on all HNs to a config file on the BackupPC server for # later use. - open my $out, ">/etc/backuppc/vzlist_bpc" || - die "Cannot write to /etc/backuppc/vzlist_bpc"; + my @HNS = (); + open my $cfg, "<$hnlistFile" || mydie "Cannot read $hnlistFile"; + while (<$cfg>) { + chomp; + push(@HNS, split(' ')) if (! /^#/); + } + close($cfg); + mydie "No HNs defined in $hnlistFile" if ($#HNS < 0); + + open my $out, ">$velistFile" || mydie "Cannot write to $velistFile"; foreach my $hn (@HNS) { open my $fh, "ssh -l root $hn vzlist -a |" || - die "Can run remote vzlist command"; + mydie "Can run remote vzlist command"; while (<$fh>) { chomp; my ($veid, $junk, $running, $junk, $hostname) = split(' '); @@ -100,8 +125,8 @@ sub refreshConfig() # For use on the BackupPC server. sub loadVeList() { - open my $fh, ") { chomp; @@ -116,29 +141,29 @@ sub loadVeList() close($fh); } -# For use on the HN +# For use on the HN. Derive the VE record from its VEID. sub localVe($) { my ($veid) = @_; - die "HN: no veid" if (!defined($veid)); + mydie "HN: no veid" if (!defined($veid)); my $vzdir = '/etc/vz'; - die "HN is not running OpenVZ" if (!defined($vzdir)); + mydie "HN is not running OpenVZ" if (!defined($vzdir)); my $lockdir = undef; my $dumpdir = undef; my $private = undef; my $root = undef; - open my $fh, "<$vzdir/vz.conf" || die "Cannot open $vzdir/vz.conf"; + open my $fh, "<$vzdir/vz.conf" || mydie "Cannot open $vzdir/vz.conf"; while (<$fh>) { chomp; my ($name, $value) = split('='); if ($name eq 'LOCKDIR') { $lockdir = $value; - die "OpenVZ LOCKDIR ($lockdir) is invalid" if (! -d $lockdir); + mydie "OpenVZ LOCKDIR ($lockdir) is invalid" if (! -d $lockdir); } elsif ($name eq 'DUMPDIR') { $dumpdir = $value; - die "OpenVZ DUMPDIR ($dumpdir) is invalid" if (! -d $dumpdir); + mydie "OpenVZ DUMPDIR ($dumpdir) is invalid" if (! -d $dumpdir); } elsif ($name eq 'VE_PRIVATE') { $private = $value; } elsif ($name eq 'VE_ROOT') { @@ -148,11 +173,11 @@ sub localVe($) close($fh); my $confdir = "$vzdir/conf"; - die "OpenVZ conf dir ($confdir) not found" if (! -d $confdir); + mydie "OpenVZ conf dir ($confdir) not found" if (! -d $confdir); my $hostname = undef; my $conffile = "$confdir/$veid.conf"; - open $fh, "<$conffile" || die "Cannot open $conffile"; + open $fh, "<$conffile" || mydie "Cannot open $conffile"; while (<$fh>) { chomp; my ($name, $value) = split('='); @@ -167,15 +192,15 @@ sub localVe($) $private =~ s/"//g; $private =~ s|/\$VEID|/$veid|g; - die "VE_PRIVATE is not defined" if (!defined($private)); - die "VE $veid private dir ($private) not found" if (! -d $private); + mydie "VE_PRIVATE is not defined" if (!defined($private)); + mydie "VE $veid private dir ($private) not found" if (! -d $private); $root =~ s|/\$VEID|/$veid|g; $root =~ s/"//g; - die "VE_ROOT is not defined" if (!defined($root)); - die "VE $veid root dir ($root) not found" if (! -d $root); + mydie "VE_ROOT is not defined" if (!defined($root)); + mydie "VE $veid root dir ($root) not found" if (! -d $root); - die "VE $veid has no HOSTNAME" if (!defined($hostname)); + mydie "VE $veid has no HOSTNAME" if (!defined($hostname)); my $status = `vzctl status $veid`; my $running = 0; @@ -210,10 +235,10 @@ sub getVeByHostname($) my ($host) = @_; my $hostname = gethostbyaddr(gethostbyname($host), AF_INET); - die "Host $host not found" if (!defined($hostname)); + mydie "Host $host not found" if (!defined($hostname)); my $ve = getVeEntry('hostname', $hostname); - die "Host $hostname is not a VE" if (!defined($ve)); + mydie "Host $hostname is not a VE" if (!defined($ve)); return $ve; } @@ -233,7 +258,7 @@ sub printVeEntry($) { my ($ve) = @_; - die "No VE to print" if (!defined($ve)); + mydie "No VE to print" if (!defined($ve)); print STDERR '{ '; #foreach my $k (keys %{$velist[0]}) { foreach my $k (keys %{$ve}) { @@ -245,7 +270,7 @@ sub printVeEntry($) sub delSnapshot($) { my ($ve) = @_; - die "No VE record for delSnapshot" if (!defined($ve)); + mydie "No VE record for delSnapshot" if (!defined($ve)); #print "delSnapshot: doing nothing for now\n"; #printVeEntry($ve); @@ -266,7 +291,7 @@ sub getDevice($) { my ($dir) = @_; - open my $fh, "df -P '$dir'|" || die "Unable to exec df"; + open my $fh, "df -P '$dir'|" || mydie "Unable to exec df"; <$fh>; # skip header my $df = <$fh>; close($fh); @@ -275,7 +300,7 @@ sub getDevice($) my $vg = undef; my $lv = undef; - open $fh, "lvscan|" || die "Unable to exec lvscan"; + open $fh, "lvscan|" || mydie "Unable to exec lvscan"; while (my $line = <$fh>) { if ($line =~ m|^\s+ACTIVE\s+\'/dev/([^/]+)/([^\']+)\'\s|) { # vg is $1, lv is $2 @@ -287,28 +312,28 @@ sub getDevice($) } close($fh); - die "Device $dev has no LVM entry for volume group" if (!defined($vg)); - die "Device $dev has no LVM entry for logical volume" if (!defined($lv)); + mydie "Device $dev has no LVM entry for volume group" if (!defined($vg)); + mydie "Device $dev has no LVM entry for logical volume" if (!defined($lv)); return ($dev, $mpoint, $vg, $lv); } sub makeSnapshot($) { my ($ve) = @_; - die "No VE record for snapshot" if (!defined($ve)); + mydie "No VE record for snapshot" if (!defined($ve)); my ($dev, $lvmpath, $vg, $lv) = getDevice($ve->{'private'}); #print "snapshot: dev=$dev, lvmpath=$lvmpath, vg=$vg, lm=$lv\n"; - die "Can't find device for VE filesystem" if (!defined($dev)); + mydie "Can't find device for VE filesystem" if (!defined($dev)); my $snaproot = "/$vzsnap"; mkpath "$snaproot" || - die "Can't create snapshot directory (backup in progress?)"; + mydie "Can't create snapshot directory (backup in progress?)"; $ve->{'snaproot'} = $snaproot; my $snapdev = "/dev/$vg/$vzsnap"; - die "Snapshot dev $snapdev exists (backup in progress?)" if (-b $snapdev); + mydie "Snapshot dev $snapdev exists (backup in progress?)" if (-b $snapdev); # FIXME: xfs_freeze hangs; without it we are likely to fail at some point. # FIXME: finding the mount point instead of hard coding. #cmdSystemOrEval("xfs_freeze -f /var/lib/vz/private"); @@ -316,32 +341,33 @@ sub makeSnapshot($) #cmdSystemOrEval("xfs_freeze -u /var/lib/vz/private"); if (! -b $snapdev) { delSnapshot($ve); - die "Failed to create snapshot device" + mydie "Failed to create snapshot device" } $ve->{'snapdev'} = $snapdev; - cmdSystemOrEval("mount -o nouuid $snapdev $snaproot"); + cmdSystemOrEval("mount -o noatime,nodiratime $snapdev $snaproot"); my $snapprivate = $ve->{'private'}; $snapprivate =~ s|/?$lvmpath/?|/$vzsnap/|; #print "snapshot: snapprivate = $snapprivate\n"; if ($snapprivate !~ /$vzsnap/ || ! -d $snapprivate) { delSnapshot($ve); - die "Wrong lvm mount point $lvmpath"; + mydie "Wrong lvm mount point $lvmpath"; } $ve->{'snapprivate'} = $snapprivate; if (! -d "/$snapprivate/etc") { delSnapshot($ve); - die "Mount failure or filesystem doesn't belong to a VE"; + mydie "Mount failure or filesystem doesn't belong to a VE"; } } sub saveConfigs($) { my ($ve) = @_; - die "No VE record for saveConfigs" if (!defined($ve)); + mydie "No VE record for saveConfigs" if (!defined($ve)); - # Copy configuration and other scripts belonging to VE into VE's snapshot + # Copy configuration and other scripts belonging to VE into VE's snapshot. + # Do this in a manner similar to that supported by vzdump for consistency. my $snapprivate = $ve->{'snapprivate'}; mkpath "$snapprivate/etc/vzdump"; my $conffile = $ve->{'conffile'}; @@ -352,19 +378,21 @@ sub saveConfigs($) } } +# Because of the complexities involved with restoring VE configuartions and +# knowing when and how to do so, this feature is not currently implemented. sub restoreConfigs($) { my ($ve) = @_; - die "No VE record for restoreConfigs" if (!defined($ve)); + mydie "No VE record for restoreConfigs" if (!defined($ve)); my $private = $ve->{'private'}; - die "Can't restore invalid private dir $private" if (! -d $private); + mydie "Can't restore invalid private dir $private" if (! -d $private); my $qprivate = $private; $qprivate =~ s|/|\\\/|g; $qprivate =~ s|/$ve->{'VEID'}$|/\$VEID|; my $root = $ve->{'root'}; - die "Can't restore invalid root dir $root" if (! -d $root); + mydie "Can't restore invalid root dir $root" if (! -d $root); my $qroot = $root; $qroot =~ s|/|\\\/|g; $qroot =~ s|/$ve->{'VEID'}$|/\$VEID|; @@ -388,13 +416,13 @@ sub restoreConfigs($) sub checkRunningClient() { - die "A backup or restore operation are already in progress" + mydie "A backup or restore operation are already in progress" if (Proc::PID::File->running({ dir => '/tmp', verify => 1 })); # Clean up any prior backup's mount point and snapshot, if it exists. # Note that the snapshot is small, so we don't really want it lying around! my $vg = undef; - open my $fh, "lvscan|" || die "Unable to exec lvscan"; + open my $fh, "lvscan|" || mydie "Unable to exec lvscan"; while (my $line = <$fh>) { if ($line =~ m|^\s+ACTIVE\s+Snapshot\s+\'/dev/([^/]+)/$vzsnap\'\s|) { $vg = $1; @@ -410,6 +438,31 @@ sub checkRunningClient() } } +sub runPing() +{ + # This command generates ping output by pinging the host specified in the + # ping command still within @ARGV. However, if the host listed therein is + # a VE, the HN must be pinged instead. + + # Get the host + my $host = shift(@ARGV); + my $cmd = join(' ', @ARGV); + + # Find $host in the list of VEs + loadVeList(); + my $hostname = gethostbyaddr(gethostbyname($host), AF_INET); + mydie "Host $host not found" if (!defined($hostname)); + my $ve = getVeEntry('hostname', $hostname); + if (defined($ve)) { + $hostname = $ve->{'HN'}; + mydie "HN is undefined for host $host" if (!defined($hostname)); + $cmd =~ s/$host/$hostname/g; + print $dbgf ": ping request for $host remapped to HN $hostname\n"; + } + + cmdExecOrEval($cmd); +} + sub runClient($) { my ($restore) = @_; @@ -417,30 +470,34 @@ sub runClient($) checkRunningClient(); my $veid = shift(@ARGV); - die "HN needs a VEID argument" if (!defined($veid)); - die "HN: no command to execute after VEID" if ($#ARGV < 0); + mydie "HN needs a VEID argument" if (!defined($veid)); + mydie "HN: no command to execute after VEID" if ($#ARGV < 0); - # Find $host in the list of VEs + # We (the HN where this code is running) must be hosting the requested VE. my $ve = localVe($veid); - die "VE $veid not found on this HN" if (!defined($ve)); + mydie "VE $veid not found on this HN" if (!defined($ve)); #printVeEntry($ve); - if (! $restore) { + if (!$restore) { cmdSystemOrEval("vzctl stop $veid >/dev/null 2>&1") if ($ve->{'running'}); makeSnapshot($ve); cmdSystemOrEval("vzctl start $veid >/dev/null 2>&1") if ($ve->{'running'}); - die "Failed to make snapshot of filesystem for VE $veid" + mydie "Failed to make snapshot of filesystem for VE $veid" if (!defined($ve->{'snaproot'})); + # Save the VE configuration from its hosted HN into the filesystem. If + # /etc is backed up, so will the VE configuration. + saveConfigs($ve); + # Make and exec the backup command. Do it in a chroot to the snapshot - # of the VE's root dir so that any relative path information in the + # of the VEs root dir so that any relative path information in the # backup command is accurate. This does mean that each VE needs rsync, # etc. my $cmd = "chroot ".$ve->{'snapprivate'}." ".join(' ', @ARGV); #print "HN: cmd |$cmd|\n"; $? = 0; cmdSystemOrEval($cmd); - my $ret = $?; # FIXME + my $ret = $?; # FIXME - modify cmdSystemOrEval to get a return value. # Remove snapshot, we're done delSnapshot($ve); @@ -463,23 +520,19 @@ sub runServer($) { my ($restore) = @_; - # Build the beginning remote command - my $remoteCmd = "/usr/bin/".basename($0); - #print "Remote command is $remoteCmd\n"; - my $host = shift(@ARGV); - die "Hostname argument required" if (!defined($host)); - die "No command to execute after hostname" if ($#ARGV < 0); + mydie "Hostname argument required" if (!defined($host)); + mydie "No command to execute after hostname" if ($#ARGV < 0); # Find $host in the list of VEs loadVeList(); my $ve = getVeByHostname($host); - die "VE $host not found" if (!defined($ve)); + mydie "$host is not a VE or list of HNs in $0 is wrong" if (!defined($ve)); #printVeEntry($ve); - # The command line is bisected by the next occurrence of $host. Everything - # before is the ssh command (sans what to run on the VE) and everything - # after is the xfer command to run on the VE. + # The client command is bisected by the next occurrence of $host. Everything + # before is the ssh command to reach the client and everything after is the + # xfer command sent to the client via ssh. my @sshCmd; my @xferCmd; my $foundHost = 0; @@ -494,79 +547,18 @@ sub runServer($) } } } - die "No ssh command found" if ($#sshCmd < 0); - die "No xfer command found" if ($#xferCmd < 0); + mydie "No ssh command found" if ($#sshCmd < 0); + mydie "No xfer command found" if ($#xferCmd < 0); #print "ssh command: |".join(' ', @sshCmd)."|\n"; #print "xfer command: |".join(' ', @xferCmd)."|\n"; - # Create command line to initiate the remote side of the backup. The - # remote side runs on the VE's HN and is given the VE's VEID. - my $cmd = join(' ', @sshCmd)." ".$ve->{'HN'}." $remoteCmd ". - ($restore ? "restore " : "").$ve->{'VEID'}." ".join(' ', @xferCmd); + # Create the command line to initiate the client side of the backup. We + # contact the HN hosting the VE and run an invocation of this script there + # to perform the required operations for VE backup or restore. + my $cmd; + $cmd = join(' ', @sshCmd)." ".$ve->{'HN'}." /usr/bin/".basename($0)." ". + ($restore ? "restore " : "").$ve->{'VEID'}." ".join(' ', @xferCmd); #print "remote command: |$cmd|\n"; - - ## Search and replace - #foreach my $key (keys %{$velist[0]}) { - # my $val = $ve->{$key}; - # $cmd =~ s/\@$key\@/$val/g if (defined($val)); - #} - - cmdExecOrEval($cmd); -} - -# A hard-coded test; didn't seem to help -sub runServer_test($) -{ - my ($restore) = @_; - - # Build the beginning remote command - my $remoteCmd = "/usr/bin/".basename($0); - #print "Remote command is $remoteCmd\n"; - - my $host = shift(@ARGV); - die "Hostname argument required" if (!defined($host)); - die "No command to execute after hostname" if ($#ARGV < 0); - - # Find $host in the list of VEs - loadVeList(); - my $ve = getVeByHostname($host); - die "VE $host not found" if (!defined($ve)); - #printVeEntry($ve); - - # The command line is bisected by the next occurrence of $host. Everything - # before is the ssh command (sans what to run on the VE) and everything - # after is the xfer command to run on the VE. - my @sshCmd; - my @xferCmd; - my $foundHost = 0; - foreach my $arg (@ARGV) { - if ($arg eq $host) { - $foundHost = 1; - } else { - if ($foundHost) { - push(@xferCmd, $arg); - } else { - push(@sshCmd, $arg); - } - } - } - die "No ssh command found" if ($#sshCmd < 0); - die "No xfer command found" if ($#xferCmd < 0); - #print "ssh command: |".join(' ', @sshCmd)."|\n"; - #print "xfer command: |".join(' ', @xferCmd)."|\n"; - - # Create command line to initiate the remote side of the backup. The - # remote side runs on the VE's HN and is given the VE's VEID. - my $cmd = join(' ', @sshCmd)." pe18002.titaniummirror.com $remoteCmd ". - ($restore ? "restore " : "")."151 ".join(' ', @xferCmd); - #print "remote command: |$cmd|\n"; - - ## Search and replace - #foreach my $key (keys %{$velist[0]}) { - # my $val = $ve->{$key}; - # $cmd =~ s/\@$key\@/$val/g if (defined($val)); - #} - cmdExecOrEval($cmd); } @@ -593,10 +585,21 @@ if ($ARGV[0] eq "restore") { #print "Restore mode\n"; } +my $ping = 0; +if ($ARGV[0] eq "ping") { + shift(@ARGV); + $ping = 1; + #print "Ping mode\n"; +} + +print $dbgf ": server=$server, refresh=$refresh, restore=$restore ping=$ping"; +print $dbgf " on ".`date`; if ($server) { runServer($restore); } elsif ($refresh) { refreshConfig(); +} elsif ($ping) { + runPing(); } else { runClient($restore); }