Rebased the branch to trunk

This commit is contained in:
Brian Fraser
2013-04-12 12:58:10 -03:00
6 changed files with 575 additions and 196 deletions

View File

@@ -3581,6 +3581,32 @@ sub name {
return $self->{hostname} || $self->{dsn_name} || 'unknown host'; return $self->{hostname} || $self->{dsn_name} || 'unknown host';
} }
sub remove_duplicate_cxns {
my ($self, %args) = @_;
my @cxns = @{$args{cxns}};
my $seen_ids = $args{seen_ids} || {};
PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
my @trimmed_cxns;
for my $cxn ( @cxns ) {
my $dbh = $cxn->dbh();
my $sql = q{SELECT @@server_id};
PTDEBUG && _d($sql);
my ($id) = $dbh->selectrow_array($sql);
PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
if ( ! $seen_ids->{$id}++ ) {
push @trimmed_cxns, $cxn
}
else {
PTDEBUG && _d("Removing ", $cxn->name,
", ID ", $id, ", because we've already seen it");
}
}
return \@trimmed_cxns;
}
sub DESTROY { sub DESTROY {
my ($self) = @_; my ($self) = @_;
@@ -3634,6 +3660,8 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use Lmo; use Lmo;
use Data::Dumper; use Data::Dumper;
{ local $EVAL_ERROR; eval { require Cxn } };
sub get_cluster_name { sub get_cluster_name {
my ($self, $cxn) = @_; my ($self, $cxn) = @_;
my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'"; my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'";
@@ -3667,6 +3695,57 @@ sub same_node {
return ($val1 || '') eq ($val2 || ''); return ($val1 || '') eq ($val2 || '');
} }
sub find_cluster_nodes {
my ($self, %args) = @_;
my $dbh = $args{dbh};
my $dsn = $args{dsn};
my $dp = $args{DSNParser};
my $make_cxn = $args{make_cxn};
my $sql = q{SHOW STATUS LIKE 'wsrep\_incoming\_addresses'};
PTDEBUG && _d($sql);
my (undef, $addresses) = $dbh->selectrow_array($sql);
PTDEBUG && _d("Cluster nodes found: ", $addresses);
return unless $addresses;
my @addresses = grep { !/\Aunspecified\z/i }
split /,\s*/, $addresses;
my @nodes;
foreach my $address ( @addresses ) {
my ($host, $port) = split /:/, $address;
my $spec = "h=$host"
. ($port ? ",P=$port" : "");
my $node_dsn = $dp->parse($spec, $dsn);
my $node_dbh = eval { $dp->get_dbh(
$dp->get_cxn_params($node_dsn), { AutoCommit => 1 }) };
if ( $EVAL_ERROR ) {
print STDERR "Cannot connect to ", $dp->as_string($node_dsn),
", discovered through $sql: $EVAL_ERROR\n";
if ( !$port && $dsn->{P} != 3306 ) {
$address .= ":3306";
redo;
}
next;
}
PTDEBUG && _d('Connected to', $dp->as_string($node_dsn));
$node_dbh->disconnect();
push @nodes, $make_cxn->(dsn => $node_dsn);
}
return \@nodes;
}
sub remove_duplicate_cxns {
my ($self, %args) = @_;
my @cxns = @{$args{cxns}};
my $seen_ids = $args{seen_ids};
return Cxn->remove_duplicate_cxns(%args);
}
sub same_cluster { sub same_cluster {
my ($self, $cxn1, $cxn2) = @_; my ($self, $cxn1, $cxn2) = @_;
@@ -3678,6 +3757,59 @@ sub same_cluster {
return ($cluster1 || '') eq ($cluster2 || ''); return ($cluster1 || '') eq ($cluster2 || '');
} }
sub autodetect_nodes {
my ($self, %args) = @_;
my $ms = $args{MasterSlave};
my $dp = $args{DSNParser};
my $make_cxn = $args{make_cxn};
my $nodes = $args{nodes};
my $seen_ids = $args{seen_ids};
my $new_nodes = [];
return $new_nodes unless @$nodes;
for my $node ( @$nodes ) {
my $nodes_found = $self->find_cluster_nodes(
dbh => $node->dbh(),
dsn => $node->dsn(),
make_cxn => $make_cxn,
DSNParser => $dp,
);
push @$new_nodes, @$nodes_found;
}
$new_nodes = $self->remove_duplicate_cxns(
cxns => $new_nodes,
seen_ids => $seen_ids
);
my $new_slaves = [];
foreach my $node (@$new_nodes) {
my $node_slaves = $ms->get_slaves(
dbh => $node->dbh(),
dsn => $node->dsn(),
make_cxn => $make_cxn,
);
push @$new_slaves, @$node_slaves;
}
$new_slaves = $self->remove_duplicate_cxns(
cxns => $new_slaves,
seen_ids => $seen_ids
);
my @new_slave_nodes = grep { $self->is_cluster_node($_) } @$new_slaves;
my $slaves_of_slaves = $self->autodetect_nodes(
%args,
nodes => \@new_slave_nodes,
);
my @autodetected_nodes = ( @$new_nodes, @$new_slaves, @$slaves_of_slaves );
return \@autodetected_nodes;
}
sub _d { sub _d {
my ($package, undef, $line) = caller 0; my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -4770,6 +4902,8 @@ sub get_slaves {
my $dp = $self->{DSNParser}; my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn}); my $methods = $self->_resolve_recursion_methods($args{dsn});
return $slaves unless @$methods;
if ( grep { m/processlist|hosts/i } @$methods ) { if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn); my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) { foreach my $arg ( @required_args ) {
@@ -8776,11 +8910,22 @@ sub main {
} }
} }
my $autodiscover_cluster;
my $recursion_method = [];
foreach my $method ( @{$o->get('recursion-method')} ) {
if ( $method eq 'cluster' ) {
$autodiscover_cluster = 1;
}
else {
push @$recursion_method, $method
}
}
$o->set('recursion-method', $recursion_method);
eval { eval {
MasterSlave::check_recursion_method($o->get('recursion-method')); MasterSlave::check_recursion_method($o->get('recursion-method'));
}; };
if ( $EVAL_ERROR ) { if ( $EVAL_ERROR ) {
$o->save_error("Invalid --recursion-method: $EVAL_ERROR") $o->save_error($EVAL_ERROR)
} }
$o->usage_or_errors(); $o->usage_or_errors();
@@ -8959,33 +9104,44 @@ sub main {
# ##################################################################### # #####################################################################
# Find and connect to slaves. # Find and connect to slaves.
# ##################################################################### # #####################################################################
$slaves = $ms->get_slaves( my $make_cxn_cluster = sub {
dbh => $master_dbh,
dsn => $master_dsn,
make_cxn => sub {
my $cxn = $make_cxn->(@_, prev_dsn => $master_cxn->dsn()); my $cxn = $make_cxn->(@_, prev_dsn => $master_cxn->dsn());
$cluster_name_for{$cxn} = $cluster->is_cluster_node($cxn); $cluster_name_for{$cxn} = $cluster->is_cluster_node($cxn);
return $cxn; return $cxn;
}, };
$slaves = $ms->get_slaves(
dbh => $master_dbh,
dsn => $master_dsn,
make_cxn => $make_cxn_cluster,
); );
# If the "master" is a cluster node, then a DSN table should have been my %seen_ids;
# used, and it may have all nodes' DSNs so the user can run the tool for my $cxn ($master_cxn, @$slaves) {
# on any node, in which case it has the "master" node, the DSN given my $dbh = $cxn->dbh();
# on the command line. So detect and remove this dupe. my $sql = q{SELECT @@server_id};
if ( $cluster_name_for{$master_cxn} ) { PTDEBUG && _d($cxn, $dbh, $sql);
@$slaves = grep { my ($id) = $dbh->selectrow_array($sql);
my $slave_cxn = $_; $seen_ids{$id}++;
if ( $cluster->same_node($master_cxn, $slave_cxn) ) {
PTDEBUG && _d('Removing ', $slave_cxn->name, 'from slaves',
'because it is the master');
0;
} }
else {
$slave_cxn; if ( $autodiscover_cluster ) {
} my @known_nodes = grep { $cluster_name_for{$_} } $master_cxn, @$slaves;
} @$slaves; my $new_cxns = $cluster->autodetect_nodes(
nodes => \@known_nodes,
MasterSlave => $ms,
DSNParser => $dp,
make_cxn => $make_cxn_cluster,
seen_ids => \%seen_ids,
);
push @$slaves, @$new_cxns;
} }
my $trimmed_nodes = Cxn->remove_duplicate_cxns(
cxns => [ $master_cxn, @$slaves ],
);
($master_cxn, @$slaves) = @$trimmed_nodes;
PTDEBUG && _d(scalar @$slaves, 'slaves found'); PTDEBUG && _d(scalar @$slaves, 'slaves found');
if ( !@$slaves && $o->get('recursion-method')->[0] ne 'none' ) { if ( !@$slaves && $o->get('recursion-method')->[0] ne 'none' ) {
$exit_status |= 1; $exit_status |= 1;
@@ -9090,7 +9246,7 @@ sub main {
warn "Diffs will only be detected if the cluster is " warn "Diffs will only be detected if the cluster is "
. "consistent with " . $direct_slave->name . " because " . "consistent with " . $direct_slave->name . " because "
. $master_cxn->name . " is a traditional replication master " . $master_cxn->name . " is a traditional replication master "
. " but these replicas are cluster nodes:\n" . "but these replicas are cluster nodes:\n"
. join("\n", map { ' ' . $_->name } @nodes) . "\n" . join("\n", map { ' ' . $_->name } @nodes) . "\n"
. "For more information, please read the Percona XtraDB " . "For more information, please read the Percona XtraDB "
. "Cluster section of the tool's documentation.\n"; . "Cluster section of the tool's documentation.\n";
@@ -11765,6 +11921,7 @@ Possible methods are:
=========== ================== =========== ==================
processlist SHOW PROCESSLIST processlist SHOW PROCESSLIST
hosts SHOW SLAVE HOSTS hosts SHOW SLAVE HOSTS
cluster SHOW STATUS LIKE 'wsrep\_incoming\_addresses'
dsn=DSN DSNs from a table dsn=DSN DSNs from a table
none Do not find slaves none Do not find slaves
@@ -11775,6 +11932,13 @@ the C<hosts> method becomes the default because it works better in this case.
The C<hosts> method requires replicas to be configured with C<report_host>, The C<hosts> method requires replicas to be configured with C<report_host>,
C<report_port>, etc. C<report_port>, etc.
The C<cluster> method requires a cluster based on Galera 23.7.3 or newer,
such as Percona XtraDB Cluster versions 5.5.29 and above. This will
autodiscover nodes in a cluster using C<SHOW STATUS LIKE 'wsrep\_incoming\_addresses'>.
Note that you can combine C<cluster> with C<processlist> and C<hosts> to
have the tool autodiscover all cluster nodes and traditional slaves,
but this is considered highly experimental.
The C<dsn> method is special: rather than automatically discovering replicas, The C<dsn> method is special: rather than automatically discovering replicas,
this method specifies a table with replica DSNs. The tool will only connect this method specifies a table with replica DSNs. The tool will only connect
to these replicas. This method works best when replicas do not use the same to these replicas. This method works best when replicas do not use the same

View File

@@ -220,6 +220,42 @@ sub name {
return $self->{hostname} || $self->{dsn_name} || 'unknown host'; return $self->{hostname} || $self->{dsn_name} || 'unknown host';
} }
# There's two reasons why there might be dupes:
# If the "master" is a cluster node, then a DSN table might have been
# used, and it may have all nodes' DSNs so the user can run the tool
# on any node, in which case it has the "master" node, the DSN given
# on the command line.
# On the other hand, maybe find_cluster_nodes worked, in which case
# we definitely have a dupe for the master cxn, but we may also have a
# dupe for every other node if this was unsed in conjunction with a
# DSN table.
# So try to detect and remove those.
sub remove_duplicate_cxns {
my ($self, %args) = @_;
my @cxns = @{$args{cxns}};
my $seen_ids = $args{seen_ids} || {};
PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
my @trimmed_cxns;
for my $cxn ( @cxns ) {
my $dbh = $cxn->dbh();
my $sql = q{SELECT @@server_id};
PTDEBUG && _d($sql);
my ($id) = $dbh->selectrow_array($sql);
PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
if ( ! $seen_ids->{$id}++ ) {
push @trimmed_cxns, $cxn
}
else {
PTDEBUG && _d("Removing ", $cxn->name,
", ID ", $id, ", because we've already seen it");
}
}
return \@trimmed_cxns;
}
sub DESTROY { sub DESTROY {
my ($self) = @_; my ($self) = @_;

View File

@@ -73,6 +73,8 @@ sub get_slaves {
my $dp = $self->{DSNParser}; my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn}); my $methods = $self->_resolve_recursion_methods($args{dsn});
return $slaves unless @$methods;
if ( grep { m/processlist|hosts/i } @$methods ) { if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn); my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) { foreach my $arg ( @required_args ) {
@@ -114,7 +116,6 @@ sub _resolve_recursion_methods {
my ($self, $dsn) = @_; my ($self, $dsn) = @_;
my $o = $self->{OptionParser}; my $o = $self->{OptionParser};
if ( $o->got('recursion-method') ) { if ( $o->got('recursion-method') ) {
# Use whatever the user explicitly gave on the command line.
return $o->get('recursion-method'); return $o->get('recursion-method');
} }
elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) {

View File

@@ -30,6 +30,8 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use Lmo; use Lmo;
use Data::Dumper; use Data::Dumper;
{ local $EVAL_ERROR; eval { require Cxn } };
sub get_cluster_name { sub get_cluster_name {
my ($self, $cxn) = @_; my ($self, $cxn) = @_;
my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'"; my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'";
@@ -63,6 +65,70 @@ sub same_node {
return ($val1 || '') eq ($val2 || ''); return ($val1 || '') eq ($val2 || '');
} }
# TODO: Check that the PXC version supports wsrep_incoming_addresses
# Not really necessary, actually. But in case it's needed,
# wsrep_provider_version =~ /[0-9]+\.[0-9]+\(r([0-9]+)\)/ && $1 >= 137
sub find_cluster_nodes {
my ($self, %args) = @_;
my $dbh = $args{dbh};
my $dsn = $args{dsn};
my $dp = $args{DSNParser};
my $make_cxn = $args{make_cxn};
# Ostensibly the caller should've done this already, but
# useful for safety.
# TODO this fails with a strange error.
#$dp->fill_in_dsn($dbh, $dsn);
my $sql = q{SHOW STATUS LIKE 'wsrep\_incoming\_addresses'};
PTDEBUG && _d($sql);
my (undef, $addresses) = $dbh->selectrow_array($sql);
PTDEBUG && _d("Cluster nodes found: ", $addresses);
return unless $addresses;
my @addresses = grep { !/\Aunspecified\z/i }
split /,\s*/, $addresses;
my @nodes;
foreach my $address ( @addresses ) {
my ($host, $port) = split /:/, $address;
my $spec = "h=$host"
. ($port ? ",P=$port" : "");
my $node_dsn = $dp->parse($spec, $dsn);
my $node_dbh = eval { $dp->get_dbh(
$dp->get_cxn_params($node_dsn), { AutoCommit => 1 }) };
if ( $EVAL_ERROR ) {
print STDERR "Cannot connect to ", $dp->as_string($node_dsn),
", discovered through $sql: $EVAL_ERROR\n";
# This is a bit strange, so an explanation is called for.
# If there wasn't a port, that means that this bug
# https://bugs.launchpad.net/percona-toolkit/+bug/1082406
# isn't fixed on this version of PXC. We tried using the
# master's port, but that didn't work. So try again, using
# the default port.
if ( !$port && $dsn->{P} != 3306 ) {
$address .= ":3306";
redo;
}
next;
}
PTDEBUG && _d('Connected to', $dp->as_string($node_dsn));
$node_dbh->disconnect();
push @nodes, $make_cxn->(dsn => $node_dsn);
}
return \@nodes;
}
sub remove_duplicate_cxns {
my ($self, %args) = @_;
my @cxns = @{$args{cxns}};
my $seen_ids = $args{seen_ids};
return Cxn->remove_duplicate_cxns(%args);
}
sub same_cluster { sub same_cluster {
my ($self, $cxn1, $cxn2) = @_; my ($self, $cxn1, $cxn2) = @_;
@@ -75,6 +141,61 @@ sub same_cluster {
return ($cluster1 || '') eq ($cluster2 || ''); return ($cluster1 || '') eq ($cluster2 || '');
} }
sub autodetect_nodes {
my ($self, %args) = @_;
my $ms = $args{MasterSlave};
my $dp = $args{DSNParser};
my $make_cxn = $args{make_cxn};
my $nodes = $args{nodes};
my $seen_ids = $args{seen_ids};
my $new_nodes = [];
return $new_nodes unless @$nodes;
for my $node ( @$nodes ) {
my $nodes_found = $self->find_cluster_nodes(
dbh => $node->dbh(),
dsn => $node->dsn(),
make_cxn => $make_cxn,
DSNParser => $dp,
);
push @$new_nodes, @$nodes_found;
}
$new_nodes = $self->remove_duplicate_cxns(
cxns => $new_nodes,
seen_ids => $seen_ids
);
my $new_slaves = [];
foreach my $node (@$new_nodes) {
my $node_slaves = $ms->get_slaves(
dbh => $node->dbh(),
dsn => $node->dsn(),
make_cxn => $make_cxn,
);
push @$new_slaves, @$node_slaves;
}
$new_slaves = $self->remove_duplicate_cxns(
cxns => $new_slaves,
seen_ids => $seen_ids
);
# If some of the new slaves is a cluster node, autodetect new nodes
# from there too.
my @new_slave_nodes = grep { $self->is_cluster_node($_) } @$new_slaves;
my $slaves_of_slaves = $self->autodetect_nodes(
%args,
nodes => \@new_slave_nodes,
);
my @autodetected_nodes = ( @$new_nodes, @$new_slaves, @$slaves_of_slaves );
return \@autodetected_nodes;
}
sub _d { sub _d {
my ($package, undef, $line) = caller 0; my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }

View File

@@ -31,7 +31,7 @@ binlog_format = ROW
wsrep_provider = LIBGALERA wsrep_provider = LIBGALERA
wsrep_cluster_address = CLUSTER_AD wsrep_cluster_address = CLUSTER_AD
wsrep_sst_receive_address = ADDR:RECEIVE_PRT wsrep_sst_receive_address = ADDR:RECEIVE_PRT
wsrep_node_incoming_address= ADDR wsrep_node_incoming_address= ADDR:PORT
wsrep_slave_threads = 2 wsrep_slave_threads = 2
wsrep_cluster_name = CLUSTER_NAME wsrep_cluster_name = CLUSTER_NAME
wsrep_provider_options = "gmcast.listen_addr=tcp://ADDR:LISTEN_PRT;" wsrep_provider_options = "gmcast.listen_addr=tcp://ADDR:LISTEN_PRT;"

View File

@@ -22,6 +22,8 @@ use PerconaTest;
use Sandbox; use Sandbox;
require "$trunk/bin/pt-table-checksum"; require "$trunk/bin/pt-table-checksum";
my $ip = qr/\Q127.1\E|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
my $dp = new DSNParser(opts=>$dsn_opts); my $dp = new DSNParser(opts=>$dsn_opts);
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $node1 = $sb->get_dbh_for('node1'); my $node1 = $sb->get_dbh_for('node1');
@@ -75,34 +77,41 @@ $output = output(
like( like(
$output, $output,
qr/h=127.1,P=12345 is a cluster node but no other nodes/, qr/h=127(?:\Q.0.0\E)?.1,P=12345 is a cluster node but no other nodes/,
"Dies if no other nodes are found" "Dies if no other nodes are found"
); );
$output = output( for my $args (
["using recusion-method=dsn", '--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns"],
["using recursion-method=cluster", '--recursion-method', 'cluster']
)
{
my $test = shift @$args;
$output = output(
sub { pt_table_checksum::main(@args, sub { pt_table_checksum::main(@args,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns") @$args)
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'errors'), PerconaTest::count_checksum_results($output, 'errors'),
0, 0,
"No diffs: no errors" "No diffs: no errors ($test)"
); );
is( is(
PerconaTest::count_checksum_results($output, 'skipped'), PerconaTest::count_checksum_results($output, 'skipped'),
0, 0,
"No diffs: no skips" "No diffs: no skips ($test)"
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
0, 0,
"No diffs: no diffs" "No diffs: no diffs ($test)"
); );
}
# Now really test checksumming a cluster. To create a diff we have to disable # Now really test checksumming a cluster. To create a diff we have to disable
# the binlog. Although PXC doesn't need or use the binlog to communicate # the binlog. Although PXC doesn't need or use the binlog to communicate
@@ -135,33 +144,40 @@ is(
"Node3 not changed" "Node3 not changed"
); );
$output = output( for my $args (
["using recusion-method=dsn", '--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns"],
["using recursion-method=cluster", '--recursion-method', 'cluster']
)
{
my $test = shift @$args;
$output = output(
sub { pt_table_checksum::main(@args, sub { pt_table_checksum::main(@args,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns") @$args)
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'errors'), PerconaTest::count_checksum_results($output, 'errors'),
0, 0,
"1 diff: no errors" "1 diff: no errors ($test)"
); );
is( is(
PerconaTest::count_checksum_results($output, 'skipped'), PerconaTest::count_checksum_results($output, 'skipped'),
0, 0,
"1 diff: no skips" "1 diff: no skips ($test)"
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
1, 1,
"1 diff: 1 diff" "1 diff: 1 diff ($test)"
) or diag($output); ) or diag($output);
# 11-17T13:02:54 0 1 26 1 0 0.021 test.t # 11-17T13:02:54 0 1 26 1 0 0.021 test.t
like( like(
$output, $output,
qr/^\S+\s+ # ts qr/^\S+\s+ # ts
0\s+ # errors 0\s+ # errors
@@ -172,8 +188,9 @@ like(
\S+\s+ # time \S+\s+ # time
test.t$ # table test.t$ # table
/xm, /xm,
"1 diff: it's in test.t" "1 diff: it's in test.t ($test)"
); );
}
# ############################################################################# # #############################################################################
# cluster, node1 -> slave, run on node1 # cluster, node1 -> slave, run on node1
@@ -205,33 +222,48 @@ $slave_dbh->do("update test.t set c='zebra' where c='z'");
# https://bugs.launchpad.net/percona-toolkit/+bug/1080385 # https://bugs.launchpad.net/percona-toolkit/+bug/1080385
# Cluster nodes default to ROW format because that's what Galeara # Cluster nodes default to ROW format because that's what Galeara
# works best with, even though it doesn't really use binlogs. # works best with, even though it doesn't really use binlogs.
$output = output( for my $args (
["using recusion-method=dsn", '--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns"],
["using recursion-method=cluster,hosts", '--recursion-method', 'cluster,hosts']
)
{
my $test = shift @$args;
# Wait for the slave to apply the binlogs from node1 (its master).
# Then change it so it's not consistent.
PerconaTest::wait_for_table($slave_dbh, 'test.t');
$sb->wait_for_slaves('cslave1');
$slave_dbh->do("update test.t set c='zebra' where c='z'");
$output = output(
sub { pt_table_checksum::main(@args, sub { pt_table_checksum::main(@args,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns") @$args)
}, },
stderr => 1, stderr => 1,
); );
like( like(
$output, $output,
qr/replica h=127.1,P=12348 has binlog_format ROW/i, qr/replica h=127(?:\Q.0.0\E)?\.1,P=12348 has binlog_format ROW/i,
"--check-binlog-format warns about slave's binlog format" "--check-binlog-format warns about slave's binlog format ($test)"
); );
# Now really test that diffs on the slave are detected. # Now really test that diffs on the slave are detected.
$output = output( $output = output(
sub { pt_table_checksum::main(@args, sub { pt_table_checksum::main(@args,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns", @$args,
qw(--no-check-binlog-format)), qw(--no-check-binlog-format)),
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
1, 1,
"Detects diffs on slave of cluster node1" "Detects diffs on slave of cluster node1 ($test)"
) or diag($output); ) or diag($output);
}
$slave_dbh->disconnect; $slave_dbh->disconnect;
$sb->stop_sandbox('cslave1'); $sb->stop_sandbox('cslave1');
@@ -267,19 +299,27 @@ is(
"Slave is changed" "Slave is changed"
); );
$output = output( for my $args (
["using recusion-method=dsn", '--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns"],
["using recursion-method=cluster,hosts", '--recursion-method', 'cluster,hosts']
)
{
my $test = shift @$args;
$output = output(
sub { pt_table_checksum::main(@args, sub { pt_table_checksum::main(@args,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns", @$args,
qw(--no-check-binlog-format -d test)), qw(--no-check-binlog-format -d test)),
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
0, 0,
"Limitation: does not detect diffs on slave of cluster node2" "Limitation: does not detect diffs on slave of cluster node2 ($test)"
) or diag($output); ) or diag($output);
}
$slave_dbh->disconnect; $slave_dbh->disconnect;
$sb->stop_sandbox('cslave1'); $sb->stop_sandbox('cslave1');
@@ -454,28 +494,43 @@ like(
like( like(
$output, $output,
qr/the direct replica of h=127.1,P=12349 was not found or specified/, qr/the direct replica of h=$ip,P=12349 was not found or specified/,
"Warns that direct replica of the master isn't found or specified", "Warns that direct replica of the master isn't found or specified",
); );
# Use the other DSN table with all three nodes. Now the tool should # Use the other DSN table with all three nodes. Now the tool should
# give a more specific warning than that ^. # give a more specific warning than that ^.
$output = output( # Originally, these tested a dsn table with all nodes; now we hijack
# those tests to also try the autodetection
for my $args (
["using recusion-method=dsn", '--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns"],
["using recursion-method=cluster,hosts", '--recursion-method', 'cluster,hosts']
)
{
my $test = shift @$args;
# Make a diff on node1. If ptc is really auto-detecting node1, then it
# should report this diff.
$node1->do("set sql_log_bin=0");
$node1->do("update test.t set c='zebra' where c='z'");
$node1->do("set sql_log_bin=1");
$output = output(
sub { pt_table_checksum::main($master_dsn, sub { pt_table_checksum::main($master_dsn,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns", @$args,
qw(-d test)) qw(-d test))
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
1, 1,
"...check all nodes: 1 diff" "...check all nodes: 1 diff ($test)"
) or diag($output); ) or diag($output);
# 11-17T13:02:54 0 1 26 1 0 0.021 test.t # 11-17T13:02:54 0 1 26 1 0 0.021 test.t
like( like(
$output, $output,
qr/^\S+\s+ # ts qr/^\S+\s+ # ts
0\s+ # errors 0\s+ # errors
@@ -486,64 +541,66 @@ like(
\S+\s+ # time \S+\s+ # time
test.t$ # table test.t$ # table
/xm, /xm,
"...check all nodes: it's in test.t" "...check all nodes: it's in test.t ($test)"
); );
like( like(
$output, $output,
qr/Diffs will only be detected if the cluster is consistent with h=127.1,P=12345 because h=127.1,P=12349/, qr/Diffs will only be detected if the cluster is consistent with h=$ip,P=12345 because h=$ip,P=12349/,
"Warns that diffs only detected if cluster consistent with direct replica", "Warns that diffs only detected if cluster consistent with direct replica ($test)",
); );
# Restore node1 so the cluster is consistent, but then make node2 differ. # Restore node1 so the cluster is consistent, but then make node2 differ.
# ptc should NOT detect this diff because the checksum query will replicate # ptc should NOT detect this diff because the checksum query will replicate
# to node1, node1 isn't different, so it broadcasts the result in ROW format # to node1, node1 isn't different, so it broadcasts the result in ROW format
# that all is ok, which node2 gets and thus false reports. This is why # that all is ok, which node2 gets and thus false reports. This is why
# those ^ warnings exist. # those ^ warnings exist.
$node1->do("set sql_log_bin=0"); $node1->do("set sql_log_bin=0");
$node1->do("update test.t set c='z' where c='zebra'"); $node1->do("update test.t set c='z' where c='zebra'");
$node1->do("set sql_log_bin=1"); $node1->do("set sql_log_bin=1");
$node2->do("set sql_log_bin=0"); $node2->do("set sql_log_bin=0");
$node2->do("update test.t set c='zebra' where c='z'"); $node2->do("update test.t set c='zebra' where c='z'");
$node2->do("set sql_log_bin=1"); $node2->do("set sql_log_bin=1");
($row) = $node2->selectrow_array("select c from test.t order by c desc limit 1"); ($row) = $node2->selectrow_array("select c from test.t order by c desc limit 1");
is( is(
$row, $row,
"zebra", "zebra",
"Node2 is changed again" "Node2 is changed again ($test)"
); );
($row) = $node1->selectrow_array("select c from test.t order by c desc limit 1"); ($row) = $node1->selectrow_array("select c from test.t order by c desc limit 1");
is( is(
$row, $row,
"z", "z",
"Node1 not changed again" "Node1 not changed again ($test)"
); );
($row) = $node3->selectrow_array("select c from test.t order by c desc limit 1"); ($row) = $node3->selectrow_array("select c from test.t order by c desc limit 1");
is( is(
$row, $row,
"z", "z",
"Node3 not changed again" "Node3 not changed again ($test)"
); );
# the other DSN table with all three nodes, but it won't matter because # the other DSN table with all three nodes, but it won't matter because
# node1 is going to broadcast the false-positive that there are no diffs. # node1 is going to broadcast the false-positive that there are no diffs.
$output = output( $output = output(
sub { pt_table_checksum::main($master_dsn, sub { pt_table_checksum::main($master_dsn,
'--recursion-method', "dsn=$node1_dsn,D=dsns,t=dsns", @$args,
qw(-d test)) qw(-d test))
}, },
stderr => 1, stderr => 1,
); );
is( is(
PerconaTest::count_checksum_results($output, 'diffs'), PerconaTest::count_checksum_results($output, 'diffs'),
0, 0,
"Limitation: diff not on direct replica not detected" "Limitation: diff not on direct replica not detected ($test)"
) or diag($output); ) or diag($output);
}
# ########################################################################### # ###########################################################################
# Be sure to stop the slave on node1, else further test will die with: # Be sure to stop the slave on node1, else further test will die with:
@@ -587,13 +644,13 @@ $output = output(
like( like(
$output, $output,
qr/h=127.1,P=12345 is in cluster pt_sandbox_cluster/, qr/h=127(?:\Q.0.0\E)?.1,P=12345 is in cluster pt_sandbox_cluster/,
"Detects that node1 is in pt_sandbox_cluster" "Detects that node1 is in pt_sandbox_cluster"
); );
like( like(
$output, $output,
qr/h=127.1,P=2900 is in cluster cluster2/, qr/h=127(?:\Q.0.0\E)?.1,P=2900 is in cluster cluster2/,
"Detects that node4 is in cluster2" "Detects that node4 is in cluster2"
); );