#!/usr/bin/perl -w # # mysql replication monitoring # generic attempt use strict; #use Data::Dumper; my $DEBUG = 0; $ENV{BBPROG} = "bb-mysql-replication"; my $TESTNAME = "mysqlrep"; my $BBHOME = $ENV{BBHOME}; my $BB = $ENV{BB}; # full path to the bin/bb util my $BBDISP = $ENV{BBDISP}; # IP of the BBDISPLAY server my $BBVAR = $ENV{BBVAR}; my $MACHINE = $ENV{MACHINE}; # hostname, fqdn my $COLOR = "clear"; # global color for the test my $MSG = ""; # body of the message my $HEAD = ""; # first line of the message (has to be short, optional) my $DATA = ""; # data for NCV records (hobbit only) if ($DEBUG == 1) { $BBHOME = "/tmp" unless $BBHOME; $BBDISP = "127.0.0.1" unless $BBDISP; $BBVAR = "/tmp" unless $BBVAR; $MACHINE = "host.priv.com" unless $MACHINE; } # The Fine Manual : sub clear; # sets status color to clear sub green; # ~ to green sub yellow; # ~ to yellow sub red; # ~ to red sub setcolor; # safely sets status color # setcolor("yellow") sub head; # sets the first status line # head("foo OK") sub msg; # adds text to the body of the status message # msg("foo" [, "bar", ...]) sub data; # adds NCV-formatted data to the status message # data(ds, value) sub sendreport; # sends the report to the BB server # sendreport() sub resetreport; # wipes the report (head, body, data, color) # resetreport() # You can always use $MSG, $HEAD, $DATA and $COLOR directly. ######## # Put your code here ######## # What you need : # on the remote masters and slaves, create: # for mysql 4.x : grant replication client on *.* to 'user'@'monitorhost' identified by 'password'; # grant select on test.* to 'user'@'monitorhost' identified by 'password'; # for mysql 3.x : grant process on *.* to 'user'@'monitorhost' identified by 'password'; # grant select on test.* to 'user'@'monitorhost' identified by 'password'; # use DBI; my $ERR; my $legalERR = "[^a-zA-Z0-9.:,_ -]"; my ($status, $maitreSql, @slaves, $esclaveSql, $login, $pass, %auth, $node); my ($positionMaitre_t1, $positionMaitre_t2, $positionEsclave, $slaveRunning, $error); my $conffile="/usr2/hobbitlocal/etc/bb-mysql-replication.cfg"; my $OKOFFSET = 100; readConfig(); foreach $node ( keys %auth ) { msg "Node: $node\n"; green; # let's be optimit ;) $maitreSql = $auth{$node}{master}; @slaves = keys %{ $auth{$node}{slaves} }; $login = $auth{$node}{user}; $pass = $auth{$node}{pass}; $MACHINE = $maitreSql; $positionMaitre_t1 = getSqlMasterStatus($maitreSql,$login,$pass,"test"); if (not defined $positionMaitre_t1) { $ERR =~ s/$legalERR/ /go; msg(sprintf("master ERROR (%s) &red %s", $maitreSql, $ERR)); head "mirroring FAILURE"; sendreport; resetreport; next; } sleep 2; foreach $esclaveSql (@slaves) { ($positionEsclave, $slaveRunning,$error) = getSqlSlaveStatus($esclaveSql,$login,$pass,"test"); next if not defined $positionEsclave; $auth{$node}{slaves}{$esclaveSql}{pos} = $positionEsclave; $auth{$node}{slaves}{$esclaveSql}{running} = $slaveRunning; } sleep 2; # on reprend la position sur le maître $positionMaitre_t2 = getSqlMasterStatus($maitreSql,$login,$pass,"test"); if (not defined $positionMaitre_t2) { head "mirroring FAILURE"; $ERR =~ s/$legalERR/ /go; msg(sprintf("master ERROR (%s) &red %s", $maitreSql, $ERR)); sendreport; resetreport; next; } msg(sprintf("master T1 at % 40s (%s)", $positionMaitre_t1, $maitreSql)); foreach $esclaveSql (@slaves) { $positionEsclave = $auth{$node}{slaves}{$esclaveSql}{pos}; $slaveRunning = $auth{$node}{slaves}{$esclaveSql}{running}; if (not defined $positionEsclave) { $ERR =~ s/$legalERR/ /go; msg "slave ERROR ($esclaveSql) &red $ERR"; next; } elsif ($slaveRunning eq "Yes") { msg "slave RUNNING ($esclaveSql)"; if ( ($positionMaitre_t1 le $positionEsclave) and ($positionEsclave le $positionMaitre_t2) ) { msg(sprintf("slave ON TIME at % 40s (%s)", $positionEsclave, $esclaveSql)); } else { my($masterfile, $masteroffset, $slavefile, $slaveoffset); ($masterfile, $masteroffset) = split(/:/, $positionMaitre_t1, 2); ($slavefile, $slaveoffset) = split(/:/, $positionEsclave, 2); msg(sprintf("slave LATE at % 40s (%s) &yellow", $positionEsclave, $esclaveSql)); if($masterfile eq $slavefile) { if ($masteroffset - $slaveoffset < $OKOFFSET) { green; } else { yellow; } } else { $masterfile =~ s/.+\.(\d+)$/$1/; $slavefile =~ s/.+\.(\d+)$/$1/; if($masterfile - $slavefile != 1) { yellow; } elsif($masteroffset < $OKOFFSET) { green; } else { yellow; } } } } else { msg "slave STOPPED ($esclaveSql) &red"; red; } if($error ne "") { red; $error =~ s/$legalERR/ /go; msg "slave ERROR $error"; } } msg(sprintf("master T2 at % 40s (%s)", $positionMaitre_t2, $maitreSql)); if ($COLOR eq "green") {head "Mirroring OK"} else {head "Mirroring FAILURE"} sendreport; resetreport; } exit 0; ############### ############### ######################## # conffile has format : # node;masterhost;slavehost1[,slavehost2,...];user;pass # (so it works in multiple slave setups) # example: # authcluster;auth-sql01.priv.com;auth-sql02.priv.com;monitor;coUIc # radius;rad-sql01.priv.com;rad-repsql01.priv.com,rad-backup01.priv.com;monitor;coUIc # add comment lines with '#' sub readConfig { my ($node, $master, $slaves, $user, $pass, @conf, $line); if (! open(CONF, "$conffile")) { head("monitoring error"); msg("Erreur à l'ouverture de $conffile : $!"); red; sendreport; exit 1; } @conf = ; close(CONF); foreach $line (@conf) { if (substr($line, 0, 1) eq "#") { next } elsif (substr($line, 0, 1) eq "\n") { next } else { ($node, $master, $slaves, $user, $pass) = split(/\s*;\s*/, $line, 5); if ($pass) { chomp $pass; $auth{$node}{master} = $master; foreach my $slave (split(",", $slaves)) { $auth{$node}{slaves}{$slave}{pos} = undef; $auth{$node}{slaves}{$slave}{running} = "not tested"; } $auth{$node}{user} = $user; $auth{$node}{pass} = $pass; } else { msg("&red invalid config : $line"); red; } } } #print Dumper \%auth; } # Retourne les infos relatives au sql maitre # <- (Position); sub getSqlMasterStatus { my ($host,$user,$pass,$base) = @_; my $port; ($host, $port) = split(/:/, $host, 2); if(not defined $port) { $port = 3306; } my $dbLink = DBI->connect("DBI:mysql:$base:$host:$port:mysql_connect_timeout=10","$user","$pass"); if (! $dbLink) { $ERR = $DBI::errstr; red; return (undef); } my $query = $dbLink->prepare("SHOW MASTER STATUS"); if (! $query->execute()) { $ERR = $DBI::errstr; red; return (undef); } my @results = $query->fetchrow_array; return "$results[0]:$results[1]"; } # Retourne les infos relatives au sql slave # <- (Exec_Master_Log_Pos, Slave_IO_Running&&Slave_SQL_Running) sub getSqlSlaveStatus { my ($host,$user,$pass,$base) = @_; my ($port, $slaverunning); ($host, $port) = split(/:/, $host, 2); if(not defined $port) { $port = 3306; } my $dbLink = DBI->connect("DBI:mysql:$base:$host:$port:mysql_connect_timeout=10","$user","$pass"); if (!$dbLink) { red; $ERR = $DBI::errstr; return (undef); } my $query = $dbLink->prepare("SHOW SLAVE STATUS"); $query->execute(); if (! $query->execute()) { $ERR = $DBI::errstr; red; return (undef); } my @results = $query->fetchrow_array; ## sloppy but works :-/ ## mysql 3.23 if ((scalar @results) == 12) { return ("$results[4]:$results[5]",$results[6], $results[10]); } ## mysql 4.0 elsif ((scalar @results) == 18) { if($results[9] eq "Yes" and $results[10] eq "Yes") { $slaverunning = "Yes" } else { $slaverunning = "No"; } return ("$results[8]:$results[16]", $slaverunning, $results[14]); } ## mysql 4.1 elsif ((scalar @results) == 33) { if($results[10] eq "Yes" and $results[11] eq "Yes") { $slaverunning = "Yes" } else { $slaverunning = "No" } return ("$results[9]:$results[21]", $slaverunning, $results[19]); } ## mysql x.q ? else { red; msg("Unsupported mysql version"); return (undef, "unknown"); } } ################### ################### #### # send the report #### sub sendreport { $MACHINE =~ s/\./,/g; my $date = localtime; my $cmd = "$BB $BBDISP \"status $MACHINE.$TESTNAME $COLOR $date $HEAD\n$DATA\n$MSG\""; if($DEBUG == 1) { print "$cmd\n"; } else { system($cmd); } } sub resetreport { $MSG = $DATA = $HEAD = ''; $COLOR = 'clear'; } # sets the global color of the test # prevents downgrading severity # clear == green < yellow < red sub setcolor { my $newcolor = shift; if ($newcolor eq "red") { $COLOR = "red"; } elsif ($COLOR eq "green" or $COLOR eq "clear") { $COLOR = "$newcolor"; } return $COLOR; } sub clear { setcolor 'clear' } sub green { setcolor 'green' } sub yellow { setcolor 'yellow' } sub red { setcolor 'red' } sub data { my ($n, $v) = @_; $DATA .= "$n: $v\n"; } sub head { $HEAD = "@_"; } sub msg { $MSG .= join("\n", @_) . "\n"; }