mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-28 08:51:44 +00:00
Rewrite and enhance lib/Percona/XtraDB/Cluster.pm and t/pt-table-checksum/pxc.t. Change how ptc handles various cluster issues. Change lib/Sandbox.pm subs like start_sandbox() and start_cluster(). PXC docs in ptc are a work in progress.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# This program is copyright 2011 Percona Inc.
|
||||
# This program is copyright 2012 Percona Inc.
|
||||
# Feedback and improvements are welcome.
|
||||
#
|
||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
@@ -19,104 +19,60 @@
|
||||
# ###########################################################################
|
||||
{
|
||||
# Package: Percona::XtraDB::Cluster
|
||||
# Percona::XtraDB::Cluster has helper methods to deal with Percona XtraDB Cluster
|
||||
# based servers
|
||||
|
||||
# Helper methods for dealing with Percona XtraDB Cluster nodes.
|
||||
package Percona::XtraDB::Cluster;
|
||||
use Mo;
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
||||
|
||||
use Mo;
|
||||
use Data::Dumper;
|
||||
|
||||
sub get_cluster_name {
|
||||
my ($self, $cxn) = @_;
|
||||
my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'";
|
||||
PTDEBUG && _d($cxn->name, $sql);
|
||||
my (undef, $cluster_name) = $cxn->dbh->selectrow_array($sql);
|
||||
return $cluster_name;
|
||||
}
|
||||
|
||||
sub is_cluster_node {
|
||||
my ($self, $cxn) = @_;
|
||||
return $self->{is_cluster_node}->{$cxn} if defined $self->{is_cluster_node}->{$cxn};
|
||||
|
||||
my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
|
||||
PTDEBUG && _d($sql);
|
||||
my $sql = "SHOW VARIABLES LIKE 'wsrep\_on'";
|
||||
PTDEBUG && _d($cxn->name, $sql);
|
||||
my $row = $cxn->dbh->selectrow_arrayref($sql);
|
||||
PTDEBUG && _d(defined $row ? @$row : 'undef');
|
||||
$self->{is_cluster_node}->{$cxn} = $row && $row->[1]
|
||||
? ($row->[1] eq 'ON' || $row->[1] eq '1')
|
||||
: 0;
|
||||
PTDEBUG && _d(Dumper($row));
|
||||
return unless $row && $row->[1] && ($row->[1] eq 'ON' || $row->[1] eq '1');
|
||||
|
||||
return $self->{is_cluster_node}->{$cxn};
|
||||
my $cluster_name = $self->get_cluster_name($cxn);
|
||||
return $cluster_name;
|
||||
}
|
||||
|
||||
sub same_node {
|
||||
my ($self, $cxn1, $cxn2) = @_;
|
||||
|
||||
my $sql = "SHOW VARIABLES LIKE 'wsrep\_sst\_receive\_address'";
|
||||
PTDEBUG && _d($cxn1->name, $sql);
|
||||
my (undef, $val1) = $cxn1->dbh->selectrow_array($sql);
|
||||
PTDEBUG && _d($cxn2->name, $sql);
|
||||
my (undef, $val2) = $cxn2->dbh->selectrow_array($sql);
|
||||
|
||||
return ($val1 || '') eq ($val2 || '');
|
||||
}
|
||||
|
||||
sub same_cluster {
|
||||
my ($self, $cxn1, $cxn2) = @_;
|
||||
return unless $self->is_cluster_node($cxn1) && $self->is_cluster_node($cxn2);
|
||||
return if $self->is_master_of($cxn1, $cxn2) || $self->is_master_of($cxn2, $cxn1);
|
||||
|
||||
my $sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_name'};
|
||||
PTDEBUG && _d($sql);
|
||||
my (undef, $row) = $cxn1->dbh->selectrow_array($sql);
|
||||
my (undef, $cxn2_row) = $cxn2->dbh->selectrow_array($sql);
|
||||
# They can't be the same cluster if one of them isn't in a cluster.
|
||||
return 0 if !$self->is_cluster_node($cxn1) || !$self->is_cluster_node($cxn2);
|
||||
|
||||
return unless $row eq $cxn2_row;
|
||||
my $cluster1 = $self->get_cluster_name($cxn1);
|
||||
my $cluster2 = $self->get_cluster_name($cxn2);
|
||||
|
||||
# Now it becomes tricky. Ostensibly clusters shouldn't have the
|
||||
# same name, but tell that to the world.
|
||||
$sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_address'};
|
||||
PTDEBUG && _d($sql);
|
||||
my (undef, $addr) = $cxn1->dbh->selectrow_array($sql);
|
||||
my (undef, $cxn2_addr) = $cxn2->dbh->selectrow_array($sql);
|
||||
|
||||
# If they both have gcomm://, then they are both the first
|
||||
# node of a cluster, so they can't be in the same one.
|
||||
return if $addr eq 'gcomm://' && $cxn2_addr eq 'gcomm://';
|
||||
|
||||
if ( $addr eq 'gcomm://' ) {
|
||||
$addr = $self->_find_full_gcomm_addr($cxn1->dbh);
|
||||
}
|
||||
elsif ( $cxn2_addr eq 'gcomm://' ) {
|
||||
$cxn2_addr = $self->_find_full_gcomm_addr($cxn2->dbh);
|
||||
}
|
||||
|
||||
# Meanwhile, if they have the same address, then
|
||||
# they are definitely part of the same cluster
|
||||
return 1 if lc($addr) eq lc($cxn2_addr);
|
||||
|
||||
# However, this still leaves us with the issue that
|
||||
# the cluster addresses could look like this:
|
||||
# node1 -> node2, node2 -> node1,
|
||||
# or
|
||||
# node1 -> node2 addr,
|
||||
# node2 -> node3 addr,
|
||||
# node3 -> node1 addr,
|
||||
# TODO No clue what to do here
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub is_master_of {
|
||||
my ($self, $cxn1, $cxn2) = @_;
|
||||
|
||||
my $cxn2_dbh = $cxn2->dbh;
|
||||
my $sql = q{SHOW SLAVE STATUS};
|
||||
PTDEBUG && _d($sql);
|
||||
local $cxn2_dbh->{FetchHashKeyName} = 'NAME_lc';
|
||||
my $slave_status = $cxn2_dbh->selectrow_hashref($sql);
|
||||
return unless ref($slave_status) eq 'HASH';
|
||||
|
||||
my $port = $cxn1->dsn->{P};
|
||||
return unless $slave_status->{master_port} eq $port;
|
||||
return 1 if $cxn1->dsn->{h} eq $slave_status->{master_host};
|
||||
|
||||
# They might be the same but in different format
|
||||
my $host = scalar gethostbyname($cxn1->dsn->{h});
|
||||
my $master_host = scalar gethostbyname($slave_status->{master_host});
|
||||
return 1 if $master_host eq $host;
|
||||
return;
|
||||
}
|
||||
|
||||
sub _find_full_gcomm_addr {
|
||||
my ($self, $dbh) = @_;
|
||||
|
||||
my $sql = q{SHOW VARIABLES LIKE 'wsrep_provider_options'};
|
||||
PTDEBUG && _d($sql);
|
||||
my (undef, $provider_opts) = $dbh->selectrow_array($sql);
|
||||
my ($prov_addr) = $provider_opts =~ m{\Qgmcast.listen_addr\E\s*=\s*tcp://([^:]+:[0-9]+)\s*;}i;
|
||||
my $full_gcomm = "gcomm://$prov_addr";
|
||||
PTDEBUG && _d("gcomm address: ", $full_gcomm);
|
||||
return $full_gcomm;
|
||||
return ($cluster1 || '') eq ($cluster2 || '');
|
||||
}
|
||||
|
||||
sub _d {
|
||||
@@ -129,3 +85,6 @@ sub _d {
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
# End Percona::XtraDB::Cluster package
|
||||
# ###########################################################################
|
||||
|
134
lib/Sandbox.pm
134
lib/Sandbox.pm
@@ -53,6 +53,20 @@ my %port_for = (
|
||||
master4 => 2901,
|
||||
master5 => 2902,
|
||||
master6 => 2903,
|
||||
node1 => 12345, # pxc...
|
||||
node2 => 12346,
|
||||
node3 => 12347,
|
||||
node4 => 2900,
|
||||
node5 => 2901,
|
||||
node6 => 2902,
|
||||
cmaster => 12349, # master -> cluster
|
||||
cslave1 => 12348, # cluster -> slave
|
||||
);
|
||||
|
||||
my %server_type = (
|
||||
master => 1,
|
||||
slave => 1,
|
||||
node => 1,
|
||||
);
|
||||
|
||||
my $test_dbs = qr/^(?:mysql|information_schema|sakila|performance_schema|percona_test)$/;
|
||||
@@ -315,9 +329,9 @@ sub ok {
|
||||
|
||||
# Dings a heartbeat on the master, and waits until the slave catches up fully.
|
||||
sub wait_for_slaves {
|
||||
my $self = shift;
|
||||
my ($self, $slave) = @_;
|
||||
my $master_dbh = $self->get_dbh_for('master');
|
||||
my $slave2_dbh = $self->get_dbh_for('slave2');
|
||||
my $slave2_dbh = $self->get_dbh_for($slave || 'slave2');
|
||||
my ($ping) = $master_dbh->selectrow_array("SELECT MD5(RAND())");
|
||||
$master_dbh->do("UPDATE percona_test.sentinel SET ping='$ping' WHERE id=1");
|
||||
PerconaTest::wait_until(
|
||||
@@ -329,14 +343,6 @@ sub wait_for_slaves {
|
||||
);
|
||||
}
|
||||
|
||||
sub _d {
|
||||
my ($package, undef, $line) = caller 0;
|
||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||
map { defined $_ ? $_ : 'undef' }
|
||||
@_;
|
||||
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||
}
|
||||
|
||||
# Verifies that master, slave1, and slave2 have a faithful copy of the mysql and
|
||||
# sakila databases. The reference data is inserted into percona_test.checksums
|
||||
# by util/checksum-test-dataset when sandbox/test-env starts the environment.
|
||||
@@ -438,62 +444,84 @@ sub set_as_slave {
|
||||
}
|
||||
|
||||
sub start_sandbox {
|
||||
my ($self, $mode, $server, $master_server) = @_;
|
||||
my $port = $port_for{$server};
|
||||
my $master_port = $master_server ? $port_for{$master_server} : '';
|
||||
my $out = `$trunk/sandbox/start-sandbox $mode $port $master_port`;
|
||||
die $out if $CHILD_ERROR;
|
||||
return $out;
|
||||
my ($self, %args) = @_;
|
||||
my @required_args = qw(type server);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my ($type, $server) = @args{@required_args};
|
||||
my $env = $args{env} || '';
|
||||
|
||||
die "Invalid server type: $type" unless $server_type{$type};
|
||||
_check_server($server);
|
||||
my $port = $port_for{$server};
|
||||
|
||||
if ( $type eq 'master') {
|
||||
my $out = `$env $trunk/sandbox/start-sandbox $type $port >/dev/null`;
|
||||
die $out if $CHILD_ERROR;
|
||||
}
|
||||
elsif ( $type eq 'slave' ) {
|
||||
die "I need a slave arg" unless $args{master};
|
||||
_check_server($args{master});
|
||||
my $master_port = $port_for{$args{master}};
|
||||
|
||||
my $out = `$env $trunk/sandbox/start-sandbox $type $port $master_port >/dev/null`;
|
||||
die $out if $CHILD_ERROR;
|
||||
}
|
||||
elsif ( $type eq 'node' ) {
|
||||
my $first_node = $args{first_node} ? $port_for{$args{first_node}} : '';
|
||||
my $out = `$env $trunk/sandbox/start-sandbox cluster $port $first_node >/dev/null`;
|
||||
die $out if $CHILD_ERROR;
|
||||
}
|
||||
|
||||
my $dbh = $self->get_dbh_for($server, $args{cxn_opts});
|
||||
my $dsn = $self->dsn_for($server);
|
||||
|
||||
return $dbh, $dsn;
|
||||
}
|
||||
|
||||
sub stop_sandbox {
|
||||
my ($self, @sandboxes) = @_;
|
||||
my @ports = @port_for{@sandboxes};
|
||||
my $out = `$trunk/sandbox/stop-sandbox @ports`;
|
||||
my $out = `$trunk/sandbox/stop-sandbox @ports >/dev/null`;
|
||||
die $out if $CHILD_ERROR;
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub start_cluster {
|
||||
my ($self, %args) = @_;
|
||||
my $cluster_size = $args{cluster_size} || 3;
|
||||
my @required_args = qw(nodes);
|
||||
foreach my $arg ( @required_args ) {
|
||||
die "I need a $arg argument" unless $args{$arg};
|
||||
};
|
||||
my ($nodes) = @args{@required_args};
|
||||
|
||||
my $out = '';
|
||||
|
||||
my ($node1, @nodes) = map {
|
||||
my $node_name = "node$_";
|
||||
$node_name = "_$node_name" while exists $port_for{$node_name};
|
||||
$port_for{$node_name} = $self->_get_unused_port();
|
||||
$node_name
|
||||
} 1..$cluster_size;
|
||||
|
||||
local $ENV{CLUSTER_NAME} = $args{cluster_name} if $args{cluster_name};
|
||||
$self->start_sandbox("cluster", $node1);
|
||||
for my $node ( @nodes ) {
|
||||
$self->start_sandbox("cluster", $node, $node1);
|
||||
foreach my $node ( @$nodes ) {
|
||||
_check_server($node);
|
||||
}
|
||||
|
||||
return ($node1, @nodes);
|
||||
}
|
||||
Test::More::diag("Starting cluster with @$nodes");
|
||||
my %connect;
|
||||
|
||||
# Lifted from Nginx::Test on CPAN
|
||||
sub _get_unused_port {
|
||||
my $port = 50000 + int (rand() * 5000);
|
||||
my $first_node = shift @$nodes;
|
||||
my ($dbh, $dsn) = $self->start_sandbox(
|
||||
type => "node",
|
||||
server => $first_node,
|
||||
env => $args{env},
|
||||
);
|
||||
$connect{$first_node} = { dbh => $dbh, dsn => $dsn };
|
||||
|
||||
while ($port++ < 64000) {
|
||||
my $sock = IO::Socket::INET->new (
|
||||
Listen => 5,
|
||||
LocalAddr => '127.0.0.1',
|
||||
LocalPort => $port,
|
||||
Proto => 'tcp',
|
||||
ReuseAddr => 1
|
||||
) or next;
|
||||
foreach my $node ( @$nodes ) {
|
||||
my ($dbh, $dsn) = $self->start_sandbox(
|
||||
server => $node,
|
||||
type => "node",
|
||||
first_node => $first_node,
|
||||
env => $args{env},
|
||||
);
|
||||
$connect{$node} = { dbh => $dbh, dsn => $dsn };
|
||||
}
|
||||
|
||||
$sock->close;
|
||||
return $port;
|
||||
}
|
||||
|
||||
die "Cannot find an open port";
|
||||
return \%connect;
|
||||
}
|
||||
|
||||
sub port_for {
|
||||
@@ -501,6 +529,14 @@ sub port_for {
|
||||
return $port_for{$server};
|
||||
}
|
||||
|
||||
sub _d {
|
||||
my ($package, undef, $line) = caller 0;
|
||||
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||
map { defined $_ ? $_ : 'undef' }
|
||||
@_;
|
||||
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
}
|
||||
# ###########################################################################
|
||||
|
Reference in New Issue
Block a user