#!/usr/bin/perl -w
#
#  mysql_full_backup_snap - get ready for full database backup
#
# This sets up the filesystem and MySQL on a slave database to take
# a full backup. Specifically, it:
#
#  - makes sure we're a slave
#  - locks everything
#  - makes an LVM snapshot of the data directory
#  - resets binlogs (on this host, not *its* master)
#  - unlocks everything
#
# When it has finished running, there will be an LVM snapshot ready
# to take as a full backup, and the binlogs will be as of that snapshot.
#
# When called with '-d' it gets rid of a snapshot it created.
#
# Note that this is written in Perl because we have to do shell-y
# things while maintaining a MySQL connection (to keep the lock).

use strict;
use DBI;
use Getopt::Std;

my $SNAPFS     = '/dev/vg0/mysql_data';    # LVM logical volume to snapshot
my $SNAPMOUNT  = '/mysql/snapshot';        # Mount point for snapshot
my $SNAPNAME   = 'mysql_full_backup_snap'; # Snapshot name
my $SNAPDEV    = "/dev/vg0/$SNAPNAME";     # Snapshot device path
my $SNAPSIZE   = '1024M';                  # Size of snapshot

# no more configuration below here.

$ENV{LVM_SUPPRESS_FD_WARNINGS} = 1;

if (defined $ARGV[0] and $ARGV[0] eq '-c')
{
    create_snapshot();
}
elsif (defined $ARGV[0] and $ARGV[0] eq '-r')
{
    remove_snapshot();
}
else
{
    print "Usage: $0 [ -c | -r ]\n";
    print "    -c    create snapshot\n";
    print "    -r    remove existing snapshot\n";
    exit 1;
}


sub create_snapshot
{
    my $dbh = db_connect();
 
    # check to see if we're a slave
    debug("Checking slave status\n");
    my $sth = $dbh->prepare("SHOW SLAVE STATUS");
    $sth->execute();
    die "This host is not a MySQL slave. Cowardly refusing to mess with its binlogs." unless $sth->rows;

    # make sure a snapshot doesn't already exist
    debug("Checking for existing snapshot\n");
    die "Snapshot already exists" if snapshot_exists();

    # lock everything
    debug("Flushing tables\n");
    $dbh->do("FLUSH TABLES WITH READ LOCK");

    # make LVM snapshot
    debug("Making LVM snapshot\n");
    my $rv = system('/usr/sbin/lvcreate', 
                    '-L', $SNAPSIZE, 
                    '-s', 
                    '-n', $SNAPNAME, 
                    $SNAPFS);
    die "lvcreate failed ($rv)" if $rv;

    # reset binlogs
    debug("Doing RESET MASTER\n");
    $dbh->do("RESET MASTER");

    # unlock everything
    debug("Unlocking tables\n");
    $dbh->do("UNLOCK TABLES");

    # mount fs
    debug("Mounting snapshot\n");
    $rv = system("/bin/mount", $SNAPDEV, $SNAPMOUNT);
    die "mount failed ($rv)" if $rv;
}


sub remove_snapshot
{
    # make sure a snapshot doesn't already exist
    debug("Checking for existing snapshot\n");
    die "Can't find snapshot $SNAPDEV to remove" unless snapshot_exists();

    if (snapshot_mounted())
    {
        debug("Unmounting snapshot\n");
        my $rv = system("/bin/umount", $SNAPDEV);
        die "Can't umount $SNAPDEV ($rv)" if $rv;
    }
    else
    {
        debug("Don't need to unmount snapshot\n");
    }

    my $rv = system("/usr/sbin/lvremove", "-f", $SNAPDEV);
    die "Error in lvremove: $rv" if $rv;
}


sub snapshot_exists
{
    my $lv = `/usr/sbin/lvdisplay $SNAPFS 2>&1`; 
    return $lv =~ /$SNAPNAME/ ? 1 : 0;
}

sub snapshot_mounted
{
    my $mounts = `/bin/mount`;
    # note that this doesn't show up as $SNAPDEV -- /vg0-thing, not /vg0/thing
    return $mounts =~ /$SNAPNAME/ ? 1 : 0;
}

sub db_connect
{
    my $dsn = "DBI:mysql:database=mysql;host=localhost";
    my $dbh = DBI->connect($dsn, "root", read_mysql_password(),
                           {'RaiseError' => 1});
    return $dbh;
}

sub read_mysql_password
{
    my $password;
    open(MY, "/root/.my.cnf") 
      or die "Can't open /root/.my.cnf to read database password: $!";
    while (<MY>)
    {
        next unless /^password=(.*)$/;
        $password = $1;
    }
    close MY;
    
    return $password if $password;
    die "Can't find 'password' entry in /root/.my.cnf";
}

sub debug
{
    my $msg = shift;
    chomp $msg;
    warn "$msg\n" if $ENV{DEBUG};
}
