Created
April 4, 2009 21:19
-
-
Save pingali/90292 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Replicate the manager | |
# http://matt.simerson.net/computing/sql/mrm/mysql_replicate_manager.pl | |
#!/usr/bin/perl -w | |
use strict; | |
=head1 NAME | |
mysql_replicate_manager.pl - Mysql Replication Manager | |
=head1 SYNOPSIS | |
A handy tool to help manage replicated MySQL servers. | |
=head1 DESCRIPTION | |
Matt Simerson, Unix Systems Engineer. I have clusters of | |
replicated MySQL clusters that I manage. Some are geographically | |
redundant, some are used strictly for load balancing. This | |
script is VERY useful for me and my co-workers. | |
In order to use this program, you must have MATT::Bundle installed. | |
See http://matt.simerson.net/computing/perl/ | |
=cut | |
####################################################################### | |
# Don't muck with anything below this line # | |
####################################################################### | |
use MATT::Perl; | |
use MATT::Mysql; | |
use MATT::Utility; | |
LoadModule("DBI", "p5-DBI", "databases" ); | |
LoadModule("DBD::mysql", "p5-Mysql", "databases"); | |
LoadModule("File::Spec"); | |
use vars qw/ $opt_a $opt_b $opt_c $opt_d $opt_f $opt_h $opt_i | |
$opt_l $opt_p $opt_q $opt_s $opt_v $opt_x $version/; | |
use Getopt::Std; | |
getopts('abcd:fhilpqs:u:vx'); | |
$version = "1.5.1"; | |
$|++; | |
my $debug = 1; $debug = 0 if ( $opt_q ); | |
&print_header if $debug; | |
if (!$opt_a && !$opt_b && !$opt_c && !$opt_f && !$opt_h && !$opt_i | |
&& !$opt_l && !$opt_p && !$opt_q && !$opt_s && !$opt_v && !$opt_x ) | |
{ | |
&mysql_print_usage; | |
exit 0; | |
}; | |
my $sudo = &sudo_setup; | |
my $dot = ParseDotFile(".my.cnf", "[mysql_replicate_manager]"); | |
my $ac = SetAutocommit($dot); | |
&mysql_backup ($opt_d) if ( $opt_b ); | |
my $db_mv = MysqlDBVars ( $dot ); | |
my $db_sv = &mysql_slave_db_vars( $dot ); | |
my ($dbh_m, $dsn_m, $drh_m) = MysqlConnect ( $db_mv ); | |
&mysql_get_vars ($dbh_m); | |
&mysql_show_master_logs ($dbh_m) if ( $opt_l ); | |
&mysql_purge_master ($dbh_m) if ( $opt_p ); | |
&mysql_purge_master_force ($dbh_m) if ( $opt_f ); | |
&mysql_archive ($dbh_m) if ( $opt_a ); | |
&mysql_copy_tarball () if ( $opt_c ); | |
&mysql_extract_archive () if ( $opt_x ); | |
if ( $opt_i ) { | |
my $ver_m = &mysql_master_info ($dbh_m, $drh_m); | |
&mysql_slave_info ($db_sv, $ver_m); | |
}; | |
&mysql_halt_master ($dbh_m, $drh_m) if ( $opt_h ); | |
&mysql_shutdown ($db_mv, $drh_m) if ( $opt_s && $opt_s =~ /^m/ ); | |
&mysql_shutdown_slaves () if ( $opt_s && $opt_s =~ /^s/ ); | |
$dbh_m->disconnect; | |
print "\n All done.\n\n" if $debug; | |
exit 1; | |
## | |
# Subroutines | |
## | |
# ---------------------------------------------------------------------- | |
sub print_header() { | |
print <<EOHEADER | |
**** **** MySQL Replication Manager $version **** **** | |
by Matt Simerson | |
EOHEADER | |
; | |
}; | |
sub mysql_print_usage() { | |
print<<EOUSAGE; | |
usage: $0 [-u <root>] [-p <pass>] | |
-a Archive the mysql databases | |
-b Back up the mysql databases [-d backupdir] | |
-c Copy archive from master to slaves | |
-h Halt MySQL master, sync up slaves, and shut down master | |
-i Informative display of replication status | |
-l show master Logs | |
-f Force purge master bin-logs (this can break your replication) | |
-p Purge master bin-logs (w/o breaking slaves) | |
-s Shutdown <master/slaves> | |
-x eXtract archive on slave(s) | |
Optional | |
-q Quiet (no debugging and status information) | |
-v Verbose | |
EOUSAGE | |
return; | |
}; | |
sub mysql_halt_master($$$) { | |
my ($dbh_m, $drh_m) = @_; | |
my $ver = mysql_version($dbh_m); | |
my $newer = is_mysql_newer("3.23.28", $ver); | |
my $query = "FLUSH LOGS"; | |
my $sth_m = MysqlQuery($dbh_m, $query); | |
sleep 5; | |
$sth_m = mysql_lock_tables($dbh_m); | |
my $status = mysql_fetch_master_status($dbh_m); | |
if ($newer) { | |
mysql_sync_slaves ( $db_sv, $status ); | |
mysql_purge_master_old_logs($dbh_m); | |
} else { | |
sleep 5; | |
mysql_sync_slaves ( $db_sv, $status ); | |
mysql_reset_master ( $dbh_m, $ver); | |
mysql_reset_slaves ( $db_sv, $ver ); | |
}; | |
print "halt_master: halting master \n" if $debug; | |
mysql_shutdown($db_mv, $drh_m); | |
}; | |
sub is_mysql_newer($$) { | |
my ($min, $cur) = @_; | |
$min =~ /^([0-9]+)\.([0-9]{1,})\.([0-9]{1,})$/; | |
my @mins = ( $1, $2, $3 ); | |
$cur =~ /^([0-9]+)\.([0-9]{1,})\.([0-9]{1,})$/; | |
my @curs = ( $1, $2, $3 ); | |
if ( $curs[0] > $mins[0] ) { return 1; }; | |
if ( $curs[1] > $mins[1] ) { return 1; }; | |
if ( $curs[2] > $mins[2] ) { return 1; }; | |
return 0; | |
}; | |
sub mysql_reset_master($$) { | |
my ($dbh_m, $ver) = @_; | |
my $query; | |
my $newer = is_mysql_newer("3.23.25", $ver); | |
if ($newer) { $query = "RESET MASTER"; } else { $query = "FLUSH MASTER"; }; | |
print "reset_master: executing $query to purge binlogs.\n" if $debug; | |
my $sth_m = MysqlQuery($dbh_m, $query); | |
$sth_m->finish; | |
}; | |
sub mysql_sync_slaves($$) { | |
my ($db_sv, $status) = @_; | |
foreach my $slave ( @$db_sv ) { | |
print "sync_slaves: waiting for slave $slave->{'host'}..." if $debug; | |
my ($dbh_s, $dsn_s, $drh_s) = MysqlConnect($slave); | |
my $query = "SELECT MASTER_POS_WAIT('$status->[0]', $status->[1])"; | |
my $sth_s = MysqlQuery( $dbh_s, $query); | |
$sth_s->finish; | |
$dbh_s->disconnect; | |
print "done.\n" if $debug; | |
}; | |
}; | |
sub mysql_reset_slaves($$) { | |
my ($db_sv, $ver) = @_; | |
my $query; | |
my $newer = is_mysql_newer("3.23.25", $ver); | |
if ($newer) { $query = "RESET SLAVE"; } else { $query = "FLUSH SLAVE"; }; | |
foreach my $slave ( @$db_sv ) { | |
print "reset_slave: resetting the slave $slave->{'host'}..." if $debug; | |
my ($dbh_s, $dsn_s, $drh_s) = MysqlConnect($slave); | |
my $sth_s = MysqlQuery($dbh_s, $query); | |
$sth_s->finish; | |
print "done.\n" if $debug; | |
}; | |
}; | |
sub mysql_archive($$) { | |
my ($dbh) = @_; | |
my $datadir = $db_mv->{'datadir'}; | |
if ($datadir =~ /\/$/ ) { | |
chop $datadir; | |
}; | |
my $backupdir = check_mysql_backupdir($opt_d); | |
if (!$sudo) { | |
if ( ! -r $datadir ) { | |
print "archive: FAILED. No read permissions on $datadir.\n"; | |
return 0; | |
}; | |
}; | |
my $sth = mysql_lock_tables($dbh); | |
my $master = &mysql_fetch_master_status($dbh); | |
#mysql_create_tarball($master, $db_mv->{'dir'}); | |
mysql_create_tarball($master, $datadir, $backupdir); | |
mysql_unlock_tables($dbh, $sth); | |
}; | |
sub check_mysql_backupdir | |
{ | |
my ($dir) = @_; | |
my $backupdir; | |
if ( $opt_d ) { | |
$backupdir = $opt_d; | |
} elsif ( $dot->{'backupdir'} ) { | |
$backupdir = $dot->{'backupdir'}; | |
} else { | |
print "archive: No backup directory! Add backupdir to my.cnf\n"; | |
return 0; | |
}; | |
if (! $sudo) { | |
if ( !-w $backupdir ) { | |
print "archive: FAILED. No write on $backupdir.\n"; | |
return 0; | |
}; | |
}; | |
}; | |
sub mysql_create_tarball($$$) { | |
my ($master, $datadir, $backupdir) = @_; | |
my ($dd, $mm, $yy) = GetTheDate("",$debug); | |
my ($up1dir,$data) = StripLastDirFromPath( $datadir ); | |
my $tarball = "$backupdir/mysql-$yy-$mm-$dd.tar"; | |
my $txt = "$backupdir/mysql-$yy-$mm-$dd.txt"; | |
my $gzip = FindTheBin("gzip"); | |
if ( -e "$tarball.gz" ) { | |
print "create_tarball: skipping, $tarball exists already!\n"; | |
} else { | |
print "creating tarball $tarball..." if $debug; | |
if ( chdir($up1dir) ) { # User might not have execute on $up1dir | |
system "$sudo tar -cf $tarball $data"; | |
system "$sudo $gzip $tarball"; | |
} else { | |
print "NOTICE: Your tarball will have full paths because you don't"; | |
print "have execute permission on $up1dir. You'll have to manually"; | |
print "adjust the location of the files in $datadir.\n"; | |
system "$sudo tar -cf $tarball $datadir"; # full paths in tarball :( | |
system "$sudo $gzip $tarball"; | |
}; | |
print "done.\n" if $debug; | |
print "create_tarball: writing master position to file\n" if $debug; | |
system "echo \"$master->[0]:$master->[1]\" > /tmp/foo"; | |
system "$sudo mv /tmp/foo $txt"; | |
}; | |
}; | |
sub mysql_copy_tarball($$) | |
{ | |
my ($dd, $mm, $yy) = GetTheDate(); | |
my ($up1dir, $data) = StripLastDirFromPath( $db_mv->{'dir'} ); | |
my $scp = FindTheBin("scp"); | |
my $tarball = "$up1dir/mysql-$yy-$mm-$dd.tar.gz"; | |
my $txt = "$up1dir/mysql-$yy-$mm-$dd.txt"; | |
if ( -r $tarball || -r $txt ) { | |
foreach my $slave ( @$db_sv ) { | |
print "copy_tarball: copying to slave systems" if $debug; | |
my $up1dir = &StripLastDirFromPath( $slave->{'dir'} ); | |
system "$sudo $scp $tarball $slave->{'host'}:$up1dir"; | |
system "$sudo $scp $txt $slave->{'host'}:$up1dir"; | |
}; | |
} else { | |
print "copy_tarball: WARNING, either $tarball or $txt does not exist or is"; | |
print " not readable. That must be fixed before the copy can succeed.\n"; | |
}; | |
}; | |
sub mysql_extract_archive($) { | |
my ($d, $m, $y) = &GetTheDate; | |
my $ssh = FindTheBin("ssh"); | |
my $tarball = "mysql-$y-$m-$d.tar.gz"; | |
my $warn = 1; | |
&mysql_shutdown_slaves($warn); | |
foreach my $slave ( @$db_sv ) { | |
my ($up1dir, $data) = &StripLastDirFromPath( $slave->{'dir'} ); | |
my $rcmd = "cd $up1dir; rm -rf $data/*; gunzip \"$tarball.gz\"; tar -xzf $tarball"; | |
#my $rcmd = "cd $up1dir; rm -rf $data/*; tar -xzf $tarball"; | |
print "executing $rcmd"; | |
system "$ssh $slave->{'host'} \"$rcmd\""; | |
}; | |
}; | |
sub mysql_shutdown_slaves(;$) { | |
my ($warn) = @_; | |
foreach my $slave ( @$db_sv ) { | |
print "shutdown_slave: connecting to $slave->{'host'}\n" if $debug; | |
my ($dbh_s, $dsn_s, $drh_s) = MysqlConnect($slave, $warn); | |
print "shutdown: shutting down server $slave->{'host'}..." if $debug; | |
my $rc = $drh_s->func('shutdown', $slave->{'host'}, $slave->{'user'}, | |
$slave->{'pass'}, 'admin'); | |
if ($debug) { | |
if ( $rc ) { print "success.\n" } else { print "failed.\n" } | |
}; | |
}; | |
}; | |
sub mysql_fetch_master_status($) { | |
my ($dbh) = @_; | |
my $query = "SHOW MASTER STATUS"; | |
if (my $sth = MysqlQuery($dbh, $query)) { | |
my $r = $sth->fetchrow_arrayref; | |
print "master_info->status: $r->[0], $r->[1]\n"; | |
$sth->finish; | |
return $r; | |
}; | |
}; | |
sub mysql_binlog_enabled { | |
my ($db_mv) = @_; | |
if ( $db_mv->{log_bin} ne "ON" ) { | |
print <<EOBINLOG; | |
Hey there! In order for this server to act as a master, binary logging | |
must be enabled! Please edit /etc/my.cnf or $db_mv->{datadir}/my.cnf and | |
add "log-bin". You must also set server-id as documented at mysql.com. | |
EOBINLOG | |
; | |
return 0; | |
}; | |
return 1; | |
}; | |
sub mysql_master_db_vars($) { | |
my ($val) = @_; | |
my ($driver, $db, $host, $port, $user, $pass, $dir); | |
if ( $val->{'driver'} && $val->{'driver'} eq "" ) { | |
$driver= "mysql" } else { $driver= $val->{'driver'} | |
}; | |
if ( $val->{'db'} && $val->{'db'} eq "" ) { | |
$db = "mysql" } else { $db = $val->{'db'} | |
}; | |
if ( $val->{'host'} && $val->{'host'} eq "" ) { | |
$host = "localhost" } else { $host = $val->{'host'} | |
}; | |
if ( $val->{'port'} && $val->{'port'} eq "" ) { | |
$port = "3306" } else { $port = $val->{'port'} | |
}; | |
if ( $val->{'user'} && $val->{'user'} eq "" ) { | |
$user = "root" } else { $user = $val->{'user'} | |
}; | |
if ( $val->{'pass'} && $val->{'pass'} eq "" ) { | |
$pass = "" } else { $pass = $val->{'pass'} | |
}; | |
if ( $val->{'dir_m'} && $val->{'dir_m'} eq "" ) { | |
$dir ="/var/db/mysql"} else { $dir = $val->{'dir_m'} | |
}; | |
my %master = ( driver => $driver, db => $db, host => $host, | |
port => $port, user => $user, pass => $pass, | |
dir => $dir ); | |
return \%master; | |
}; | |
sub mysql_slave_db_vars($) { | |
my ($val) = @_; | |
my ($driver, $db, $host, $port, $user, $pass, $dir); | |
my @array; | |
if ( $val->{'driver'} && $val->{'driver'} eq "" ) { | |
$driver= "mysql" } else { $driver= $val->{'driver'} | |
}; | |
if ( $val->{'db'} && $val->{'db'} eq "" ) { | |
$db = "mysql" } else { $db = $val->{'db'} | |
}; | |
if ( $val->{'port'} && $val->{'port'} eq "" ) { | |
$port = "3306" } else { $port = $val->{'port'} | |
}; | |
if ( $val->{'user'} && $val->{'user'} eq "" ) { | |
$user = "root" } else { $user = $val->{'user'} | |
}; | |
if ( $val->{'pass'} && $val->{'pass'} eq "" ) { | |
$pass = "" } else { $pass = $val->{'pass'} | |
}; | |
if ( $val->{'dir_s'} && $val->{'dir_s'} eq "" ) { | |
$dir="/var/db/mysql"} else { $dir = $val->{'dir_s'} | |
}; | |
if ( $val->{'slaves'} ne "" ) { | |
my @hosts = split(/ /, $val->{'slaves'}); | |
foreach my $host (@hosts) { | |
my %slave = ( driver => $driver, db => $db, host => $host, | |
port => $port, user => $user, pass => $pass, | |
dir => $dir ); | |
push @array, \%slave; | |
}; | |
}; | |
return \@array; | |
}; | |
sub mysql_backup($) { | |
my ($dir) = @_; | |
my ($backupfile); | |
my $backupdir = check_mysql_backupdir($dir); | |
my $gzip = FindTheBin("gzip"); | |
my $cronolog = FindTheBin("cronolog"); | |
my $mysqldump = FindTheBin("mysqldump"); | |
if (! $cronolog or ! $gzip ) { | |
die "You must have cronolog and gzip installed!\n"; | |
}; | |
my $mysqlopts = "--all-databases --opt --password=$dot->{'pass'}"; | |
my ($dd, $mm, $yy) = &GetTheDate; | |
if ( $dir ) { | |
$backupdir = $dir; | |
} else { | |
if ( $dot->{'backupdir'} ) { | |
$backupdir = $dot->{'backupdir'}; | |
} else { | |
$backupdir = "/var/backups/mysql"; | |
}; | |
}; | |
if ( $dot->{'backupfile'} ) { | |
$backupfile = $dot->{'backupfile'}; | |
} else { | |
$backupfile = "mysql_full_dump"; | |
}; | |
if ( !-e "$backupdir/$yy/$mm/$dd/$backupfile" && | |
!-e "$backupdir/$yy/$mm/$dd/$backupfile.gz" ) { | |
system "$mysqldump $mysqlopts | $cronolog $backupdir/%Y/%m/%d/$backupfile"; | |
print "backup: running $gzip $backupdir/$yy/$mm/$dd/$backupfile\n" if $debug; | |
system "$gzip $backupdir/$yy/$mm/$dd/$backupfile"; | |
} else { | |
print "Yikes! Backup for today is already done!\n"; | |
}; | |
}; | |
sub mysql_get_hashes($$) { | |
my ($dbh, $sql) = @_; | |
my @records; | |
if (my $sth = MysqlQuery($dbh, $sql)) { | |
while (my $ref = $sth->fetchrow_hashref) { | |
push @records, $ref; | |
} | |
$sth->finish; | |
} | |
return @records; | |
}; | |
sub mysql_get_vars($) { | |
my ($dbh) = @_; | |
#my (%vars, %status); | |
my @rows = mysql_get_hashes($dbh, "SHOW VARIABLES"); | |
print "mysql_get_vars-> reading variables.\n" if $opt_v; | |
foreach my $row (@rows) { | |
my $name = $row->{Variable_name}; | |
my $value = $row->{Value}; | |
%$db_mv->{$name} = $value; | |
#$vars{$name} = $value; | |
} | |
print "mysql_get_vars-> reading extended status.\n" if $opt_v; | |
@rows = mysql_get_hashes($dbh, "SHOW STATUS"); | |
foreach my $row (@rows) { | |
my $name = $row->{Variable_name}; | |
my $value = $row->{Value}; | |
%$db_mv->{$name} = $value; | |
#$status{$name} = $value; | |
} | |
#return \%vars, \%status; | |
}; | |
## | |
# MySQL Reporting | |
## | |
# ---------------------------------------------------------------------- | |
sub mysql_master_info($$) { | |
my ($dbh, $drh) = @_; | |
my (%val_m, $db_name, $status); | |
$val_m{'ver'} = &mysql_version($dbh); | |
print "master_info->version: $val_m{'ver'}\n"; | |
my $binlog = mysql_binlog_enabled($db_mv); | |
if ($binlog) { | |
$status = &mysql_fetch_master_status($dbh); | |
$val_m{'log'} = $status->[0]; | |
$val_m{'pos'} = $status->[1]; | |
}; | |
print "data dir: $db_mv->{datadir}\n" if ($db_mv->{datadir}); | |
print "conf dir: $db_mv->{dir}\n" if ($db_mv->{dir}); | |
if ( $opt_v ) { | |
print "master_info->databases: "; | |
mysql_list_databases($dbh); | |
my @tables = $dbh->tables(); | |
print "master_info->tables: @tables\n"; | |
my $threadId = $dbh->{'thread_id'}; | |
print "master_info->threadId: $threadId\n"; | |
#foreach my $var (keys %$db_mv) { | |
# if ( $db_mv->{$var} ) { | |
# printf "%25s: %s\n", $var, $db_mv->{$var}; | |
# }; | |
#}; | |
#print "master_info->extended: \n"; | |
#mysql_show_status($dbh); | |
#my $infoString = $dbh->{'info'}; | |
#print "master_info->infostring: $infoString\n"; | |
#my $insertId = $dbh->{'mysql_insertid'}; | |
#print "master_info->insertID: $insertId\n"; | |
}; | |
print "\n"; | |
return \%val_m; | |
}; | |
sub mysql_slave_info($$) { | |
my ($db_sv, $ver_m) = @_; | |
foreach my $slave ( @$db_sv ) { | |
print "slave_info: connecting to $slave->{'host'}\n" if ($opt_v); | |
my ($dbh_s, $dsn_s, $drh_s) = &MysqlConnect($slave); | |
my $ver = &mysql_version($dbh_s); | |
print "slave_info->version: $ver\n"; | |
if ( $ver ne $ver_m->{'ver'} ) { | |
print "\n\tWARNING: The MySQL Server version on this slave ($ver) is not\n"; | |
print "\tthe same version as the master($ver_m->{'ver'}). Although\n"; | |
print "\tthis might work fine, there are known problems with replication\n"; | |
print "\tand varying versions of MySQL. I recommend updating all servers\n"; | |
print "\tto the same version! \n\n"; | |
}; | |
my $query = "SHOW SLAVE STATUS"; | |
my $sth_s = MysqlQuery($dbh_s, $query); | |
print "slave_info->status: "; | |
my ($host, $user, $port, $retry, $file, $pos, $run) = $sth_s->fetchrow_array; | |
print "$file, $pos, $run, $slave->{'host'}\n"; | |
print "\t WARNING: Slave not in sync! \n" if ( $pos ne $ver_m->{'pos'} ); | |
print "\t WARNING: Slave not running! \n" if ( $run ne "Yes" ); | |
if ($opt_v) { | |
print "slave_info->databases: "; | |
&mysql_list_databases($dbh_s); | |
#print "slave_info->extended: \n"; | |
#&mysql_show_status($dbh_s); | |
my @tables = $dbh_m->tables(); | |
print "slave_info->tables: @tables \n"; | |
}; | |
print "\n"; | |
$sth_s->finish; | |
$dbh_s->disconnect; | |
}; | |
}; | |
sub mysql_show_status($) { | |
my ($dbh) = @_; | |
if (my $sth = MysqlQuery($dbh, "SHOW STATUS")) { | |
while ( my $r = $sth->fetchrow_arrayref ) { | |
print "\t\t\t $r->[0] \t $r->[1]\n"; | |
}; | |
$sth->finish; | |
}; | |
}; | |
## | |
# MySQL Logging | |
## | |
# ---------------------------------------------------------------------- | |
sub mysql_get_master_logs($) { | |
my ($dbh_m) = @_; | |
my @vars; | |
my $query = "SHOW MASTER LOGS"; | |
my $sth_m = MysqlQuery($dbh_m, $query); | |
if (!$ac) { | |
$dbh_m->commit or die "couldn't commit: $sth_m->errstr\n"; | |
}; | |
while ( my $r = $sth_m->fetchrow_arrayref ) { | |
print "\t$r->[0]\t$r->[1]\n" if $opt_v; | |
chomp $r->[0]; | |
if ( ! $sth_m->err) { | |
push @vars, $r->[0]; | |
} else { | |
print "WARNING: $sth_m->errstr\n"; | |
}; | |
}; | |
$sth_m->finish; | |
return \@vars; | |
}; | |
sub mysql_show_master_logs($) { | |
my ($dbh_m) = @_; | |
my $logs = mysql_get_master_logs($dbh_m); | |
print "show_master_logs: (SHOW MASTER LOGS)\n\n" if ($debug); | |
foreach my $log ( @$logs ) { | |
print "\t$log\n"; | |
}; | |
}; | |
sub mysql_purge_master_old_logs($) { | |
my ($dbh_m) = @_; | |
my ($last, $sec); | |
my $logs = mysql_get_master_logs($dbh_m); | |
foreach my $l ( @$logs ) { | |
if ($l ne "") { | |
$sec = $last; | |
$last = $l; | |
}; | |
print "last: $last\tsecond: $sec\n" if $debug; | |
}; | |
my $query = "PURGE MASTER LOGS TO '$sec'"; | |
my $sth = MysqlQuery($dbh_m, $query); | |
if (!$ac) { | |
$dbh_m->commit or die "couldn't commit: $sth->errstr\n"; | |
}; | |
}; | |
sub mysql_purge_master($$) { | |
my ($dbh_m) = @_; | |
my $ver = mysql_version($dbh_m); | |
my $newer = is_mysql_newer("3.23.28", $ver); | |
my $query = "FLUSH LOGS"; | |
my $sth_m = MysqlQuery($dbh_m, $query); | |
sleep 5; | |
$sth_m = mysql_lock_tables($dbh_m); | |
my $status = mysql_fetch_master_status($dbh_m); | |
if ($newer) { | |
mysql_sync_slaves ( $db_sv, $status ); | |
mysql_purge_master_old_logs($dbh_m); | |
} else { | |
sleep 5; | |
mysql_sync_slaves ( $db_sv, $status ); | |
mysql_reset_master ( $dbh_m, $ver); | |
mysql_reset_slaves ( $db_sv, $ver ); | |
}; | |
print "purge_master: unlocking master \n" if $debug; | |
mysql_unlock_tables($dbh_m, $sth_m); | |
}; | |
sub mysql_purge_master_force($) { | |
my ($dbh_m) = @_; | |
print "WARNING! Proceeding will break any existing replication! Are you sure? "; | |
my $ans = YesOrNo(); | |
if ( $ans ) { | |
my $ver = mysql_version($dbh_m); | |
my $sth_m = mysql_lock_tables($dbh_m); | |
mysql_reset_master ( $dbh_m, $ver); | |
my $status = mysql_fetch_master_status($dbh_m); | |
print "\n\n WARNING! You just broke your replication! To restore it, you'll\n"; | |
print " need to restore a snapshot of your database on each slave and then\n"; | |
print " execute something like this command on each slave server:\n\n"; | |
print " CHANGE MASTER TO MASTER_HOST='$db_mv->{'host'}', MASTER_USER"; | |
print "='$db_mv->{'user'}', MASTER_PASSWORD='$db_mv->{'pass'}', "; | |
print "MASTER_LOG_FILE='$status->[0]', MASTER_LOG_POS=$status->[1]\n"; | |
mysql_unlock_master($dbh_m, $sth_m); | |
} else { | |
print "\npurge_master_force: Cancelled! Good choice.\n\n"; | |
}; | |
}; | |
## | |
# General MySQL Subroutines | |
## | |
# ---------------------------------------------------------------------- | |
sub mysql_shutdown($$) { | |
my ($db_v, $drh) = @_; | |
print "shutdown: shutting down server $db_v->{'host'}..." if $debug; | |
my $rc = $drh->func('shutdown', $db_v->{'host'}, $db_v->{'user'}, | |
$db_v->{'pass'}, 'admin'); | |
if ($debug) { | |
print "mysql_shutdown->rc: $rc\n"; | |
if ( $rc ) { print "success.\n" } else { print "failed.\n" } | |
}; | |
}; | |
sub mysql_list_databases($) { | |
my ($dbh) = @_; | |
if (my $sth = MysqlQuery($dbh, "SHOW DATABASES")) { | |
while ( my ($db_name) = $sth->fetchrow_array ) { | |
print "$db_name "; | |
}; | |
if ($sth->err) { | |
print "FAILED!\n"; | |
} else { | |
$sth->finish; | |
print "\n"; | |
}; | |
}; | |
### Documented (but non-working methods for listing databases ### | |
# my @databases = $drh->func($db_mv->{'host'}, $db_mv->{'port'}, '_ListDBs'); | |
# print "mysql_info->databases:\t@databases\n"; | |
# | |
# my @databases2 = DBI->data_sources("mysql"); | |
# print "mysql_info->databases2:\t@databases2\n"; | |
}; | |
sub mysql_version($) { | |
my ($dbh) = @_; | |
my ($sth, $minor); | |
if (my $sth = MysqlQuery($dbh, "SELECT VERSION()")) { | |
my $r = $sth->fetchrow_arrayref; | |
($minor) = split(/-/, $r->[0]); | |
$sth->finish; | |
}; | |
return $minor; | |
}; | |
sub mysql_lock_tables($) { | |
my ($dbh) = @_; | |
# Table locking is done at the per-thread level. If we did a $sth->finish | |
# the thread would end and we'd lose our lock. So, instead we pass the $sth | |
# handle back and close it after we've done our deeds. | |
print "lock_tables: locking tables.\n" if $debug; | |
if (my $sth = MysqlQuery($dbh, "FLUSH TABLES WITH READ LOCK")) { | |
return $sth; | |
}; | |
}; | |
sub mysql_unlock_tables($$) { | |
my ($dbh, $sth) = @_; | |
print "unlock_tables: unlocking mysql tables.\n" if $debug; | |
my $query = "UNLOCK TABLES"; # unnecessary, simply calling finish does this | |
$sth = MysqlQuery($dbh, $query) | |
or die "FATAL: couldn't unlock tables: $sth->errstr\n"; | |
$sth->finish; | |
}; | |
## | |
# Utility Subroutines (general Unix stuff) | |
## | |
# ---------------------------------------------------------------------- | |
sub sudo_setup() { | |
my $sudo = ""; | |
use POSIX qw(uname); | |
my $sudobin = FindTheBin("sudo"); | |
if (! $sudobin ) { | |
if ( $< eq 0 ) { | |
my $os = (uname)[0]; | |
if ( $os eq "FreeBSD" ) { | |
print "sudo is not installed, shall I install it? "; | |
my $answer = YesOrNo; | |
if ($answer) { | |
InstallPort("sudo", "security"); | |
$sudobin = FindTheBin("sudo"); | |
} else { | |
print "very well then, continuing.\n"; | |
return $sudo; | |
}; | |
} else { | |
print "WARNING: Sudo is not installed. Some features will not \n"; | |
print "work for non-root users.\n"; | |
return $sudo; | |
}; | |
} else { | |
print "\n\n\tWARNING: Couldn't find sudo. Some features require root "; | |
print "permissions and will not work without it. If it's installed, "; | |
print "please report it's location to Matt so future versions of "; | |
print "this script will know where to find it. \n\n"; | |
}; | |
}; | |
if ( $< ne "0" ) { | |
$sudo = "$sudobin -p 'Password for %u@%h:'"; | |
}; | |
return $sudo; | |
}; | |
1; | |
__END__ | |
=head2 Dependencies | |
In order to use this program, you must have MATT::Bundle installed. | |
See http://matt.simerson.net/computing/perl/ | |
=head1 AUTHOR | |
Matt Simerson <[email protected]> | |
=head1 BUGS | |
None known. Report any to author. | |
=head1 TODO | |
Add MySQL slave promotion feature | |
Make restoring a failed slave brain dead easy | |
Documentation, documentation, documentation | |
Add commit/rollback support where appropriate | |
check $sth->err and report any premature returns | |
=head1 SEE ALSO | |
http://matt.simerson.net/computing/sql/mrm | |
http://matt.simerson.net/computing/sql/mrm/faq.shtml | |
=head1 COPYRIGHT | |
Copyright 2003, Matt Simerson. All Right Reserved. | |
=cut |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment