Merge lp:~percona-toolkit-dev/percona-toolkit/fix-1062563-1063912-ptc-pxc-bugs into lp:percona-toolkit/2.1

Proposed by Brian Fraser
Status: Superseded
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-1062563-1063912-ptc-pxc-bugs
Merge into: lp:percona-toolkit/2.1
Diff against target: 1196 lines (+834/-103)
10 files modified
bin/pt-config-diff (+1/-15)
bin/pt-kill (+9/-19)
bin/pt-online-schema-change (+9/-19)
bin/pt-table-checksum (+144/-25)
lib/Cxn.pm (+1/-15)
lib/Percona/XtraDB/Cluster.pm (+123/-0)
lib/Sandbox.pm (+85/-4)
t/lib/Cxn.t (+4/-6)
t/lib/Percona/XtraDB/Cluster.t (+216/-0)
t/pt-table-checksum/pxc.t (+242/-0)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/fix-1062563-1063912-ptc-pxc-bugs
Reviewer Review Type Date Requested Status
Daniel Nichter Needs Fixing
Review via email: mp+130191@code.launchpad.net

This proposal has been superseded by a proposal from 2012-11-08.

To post a comment you must log in.
417. By Daniel Nichter

Merge fix-pt-upgrade-select-bug-1060774

418. By Daniel Nichter

Merge fix-938068-ptc-slave-binlog-formats

419. By Daniel Nichter

Merge fix-978133-remove-pqd-priv-checks

420. By Daniel Nichter

Redirect sys cmd 2>/dev/null in pt-kill tests to avoid false-positive errors.

421. By Daniel Nichter

Fix RawLogParser.t. Use diag in PerconaTest.pm.

422. By Daniel Nichter

Remove all_privs tests. Update Percona::Toolkit::VERSION.

423. By Daniel Nichter

Make CompareResults.t stable--yet another case of not waiting for replication.

424. By Daniel Nichter

Merge fix-pt-osc-del-trg-bug-1062324.

425. By Daniel Nichter

Merge opt-parsing-exit-status-bug-1039074.

426. By Daniel Nichter

Merge quiet-progress-bug-1039541.

427. By Daniel Nichter

Merge pdl-partitions-bug-1043528.

428. By Daniel Nichter

Merge pt-stalk-iter-1-bug-1070434.

429. By Daniel Nichter

Merge find_my_cnf_file-bug-1070916.

430. By Daniel Nichter

Remove $Sandbox::Percona::Toolkit::VERSION and use $Percona::Toolkit::VERSION instead since it's the authoritative version.

431. By Daniel Nichter

Add deprecation notice to pt-log-player for PT 2.2.

432. By Daniel Nichter

Fix t/pt-slave-delay/auto_restart.t and use direct call, no backticks.

433. By Daniel Nichter

Don't use literal values for t/pt-heartbeat/basics.t 'It is being updated' test. Use direct call rather than backticks.

434. By Daniel Nichter

Make pt-archiver --sleep tests more precise and reliable.

435. By Daniel Nichter

Merge fix-ptc-slave-binglog-formats-on-5.0.

436. By Daniel Nichter

Merge pt-find-docu-bug-1013407

437. By Daniel Nichter

Merge fix-995896-cat-in-daemon

438. By Brian Fraser

Merged fix-821715-enable-local-infile-in-dsn

439. By Brian Fraser

Merged fix-1052722-pt-fifo-split-n-minus-1-rows-initially

440. By Brian Fraser

Updated modules in all tools

Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

Are the new subs in Sandbox.pm and how Cxn.t works with a cluster vestiges of portable-test-suite, or can we do this stuff now with the sandbox/ scripts?

review: Needs Information
Revision history for this message
Brian Fraser (fraserbn) wrote :

->start_sandbox, ->stop_sandbox and ->port_for are pseudo-vestigial (they are wrappers that aren't needed, but I find ->start_sandbox(master => "master3") simpler than `$trunk/sandbox/start-sandbox master 2900` -- mostly because I keep getting the port numbers numbers wrong).

set_as_slave and start_cluster are there mostly for convenience and brevity.

But since they are all ultimately wrappers around the sandbox/ scripts, sure, it could be done.

441. By Daniel Nichter

Merge fix-938660-ptc-chunk-size-limit-0

442. By Daniel Nichter

Merge pt-show-grant-col-privs-bug-866075

443. By Brian Fraser

Merged fix-1059732-ptc-hash-functions

444. By Brian Fraser

bin/pt-table-checksum: Missing word in an error message

445. By Daniel Nichter

Merge fix-1009510-1039569-ptc-check-table-on-replicas

446. By Daniel Nichter

Merged fix-i26211-1058285-821722-implicit-ansi_quotes

Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

The PXC code in Cxn.pm doesn't seem to belong there. Let's move it and its tests to separate files, i.e. Percona/XtraDB/Cluster.pm (or maybe /XtraDBCluster.pm, but that looks a little cluttered to me). Then like I did with t/pt-table-checksum/pxc.c, we can plan skip_all at the start if the sandbox isn't running PXC.

review: Needs Fixing
447. By Brian Fraser

Merged fix-1073532-Mo-Scalar-Util-PP

448. By Brian Fraser

Merged sandbox-get_dbh_for-fix

449. By Brian Fraser

Removed the L option from the dsn_opts exported by PerconaTest, as it was useless and breaking tests, and added an L=1 to a leftover pt-archiver --bulk-insert call

450. By Daniel Nichter

Merge fix-pt-fel-bug-1075773

451. By Brian Fraser

Merged fix-1062563-1063912-ptc-pxc-bugs and resolved conflicts, added the missing Percona::XtraDB::Cluster file

452. By Brian Fraser

Reverted a few unnecessary changes

453. By Brian Fraser

Split the Percona::XtraDB::Cluster tests in two files: One that requires a PXC sandbox, and a general tests file

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/pt-config-diff'
2--- bin/pt-config-diff 2012-10-31 09:18:34 +0000
3+++ bin/pt-config-diff 2012-11-08 20:42:20 +0000
4@@ -1466,6 +1466,7 @@
5 use warnings FATAL => 'all';
6 use English qw(-no_match_vars);
7 use Scalar::Util qw(blessed);
8+
9 use constant {
10 PTDEBUG => $ENV{PTDEBUG} || 0,
11 PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
12@@ -1572,21 +1573,6 @@
13 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
14 }
15
16-sub is_cluster_node {
17- my ($self) = @_;
18- return $self->{is_cluster_node} if defined $self->{is_cluster_node};
19-
20- my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
21- PTDEBUG && _d($sql);
22- my $row = $self->{dbh}->selectrow_arrayref($sql);
23- PTDEBUG && _d(defined $row ? @$row : 'undef');
24- $self->{is_cluster_node} = $row && $row->[1]
25- ? ($row->[1] eq 'ON' || $row->[1] eq '1')
26- : 0;
27-
28- return $self->{is_cluster_node};
29-}
30-
31 sub DESTROY {
32 my ($self) = @_;
33 if ( $self->{dbh}
34
35=== modified file 'bin/pt-kill'
36--- bin/pt-kill 2012-11-07 07:12:37 +0000
37+++ bin/pt-kill 2012-11-08 20:42:20 +0000
38@@ -2478,16 +2478,20 @@
39 $Data::Dumper::Sortkeys = 1;
40 $Data::Dumper::Quotekeys = 0;
41
42+local $EVAL_ERROR;
43+eval {
44+ require Quoter;
45+};
46+
47 sub new {
48 my ( $class, %args ) = @_;
49- my @required_args = qw(Quoter);
50- foreach my $arg ( @required_args ) {
51- die "I need a $arg argument" unless $args{$arg};
52- }
53 my $self = { %args };
54+ $self->{Quoter} ||= Quoter->new();
55 return bless $self, $class;
56 }
57
58+sub Quoter { shift->{Quoter} }
59+
60 sub get_create_table {
61 my ( $self, $dbh, $db, $tbl ) = @_;
62 die "I need a dbh parameter" unless $dbh;
63@@ -4711,6 +4715,7 @@
64 use warnings FATAL => 'all';
65 use English qw(-no_match_vars);
66 use Scalar::Util qw(blessed);
67+
68 use constant {
69 PTDEBUG => $ENV{PTDEBUG} || 0,
70 PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
71@@ -4817,21 +4822,6 @@
72 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
73 }
74
75-sub is_cluster_node {
76- my ($self) = @_;
77- return $self->{is_cluster_node} if defined $self->{is_cluster_node};
78-
79- my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
80- PTDEBUG && _d($sql);
81- my $row = $self->{dbh}->selectrow_arrayref($sql);
82- PTDEBUG && _d(defined $row ? @$row : 'undef');
83- $self->{is_cluster_node} = $row && $row->[1]
84- ? ($row->[1] eq 'ON' || $row->[1] eq '1')
85- : 0;
86-
87- return $self->{is_cluster_node};
88-}
89-
90 sub DESTROY {
91 my ($self) = @_;
92 if ( $self->{dbh}
93
94=== modified file 'bin/pt-online-schema-change'
95--- bin/pt-online-schema-change 2012-11-07 07:12:37 +0000
96+++ bin/pt-online-schema-change 2012-11-08 20:42:20 +0000
97@@ -2712,16 +2712,20 @@
98 $Data::Dumper::Sortkeys = 1;
99 $Data::Dumper::Quotekeys = 0;
100
101+local $EVAL_ERROR;
102+eval {
103+ require Quoter;
104+};
105+
106 sub new {
107 my ( $class, %args ) = @_;
108- my @required_args = qw(Quoter);
109- foreach my $arg ( @required_args ) {
110- die "I need a $arg argument" unless $args{$arg};
111- }
112 my $self = { %args };
113+ $self->{Quoter} ||= Quoter->new();
114 return bless $self, $class;
115 }
116
117+sub Quoter { shift->{Quoter} }
118+
119 sub get_create_table {
120 my ( $self, $dbh, $db, $tbl ) = @_;
121 die "I need a dbh parameter" unless $dbh;
122@@ -3341,6 +3345,7 @@
123 use warnings FATAL => 'all';
124 use English qw(-no_match_vars);
125 use Scalar::Util qw(blessed);
126+
127 use constant {
128 PTDEBUG => $ENV{PTDEBUG} || 0,
129 PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
130@@ -3447,21 +3452,6 @@
131 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
132 }
133
134-sub is_cluster_node {
135- my ($self) = @_;
136- return $self->{is_cluster_node} if defined $self->{is_cluster_node};
137-
138- my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
139- PTDEBUG && _d($sql);
140- my $row = $self->{dbh}->selectrow_arrayref($sql);
141- PTDEBUG && _d(defined $row ? @$row : 'undef');
142- $self->{is_cluster_node} = $row && $row->[1]
143- ? ($row->[1] eq 'ON' || $row->[1] eq '1')
144- : 0;
145-
146- return $self->{is_cluster_node};
147-}
148-
149 sub DESTROY {
150 my ($self) = @_;
151 if ( $self->{dbh}
152
153=== modified file 'bin/pt-table-checksum'
154--- bin/pt-table-checksum 2012-11-07 07:12:37 +0000
155+++ bin/pt-table-checksum 2012-11-08 20:42:20 +0000
156@@ -21,6 +21,7 @@
157 OptionParser
158 Mo
159 Cxn
160+ Percona::XtraDB::Cluster
161 Quoter
162 VersionParser
163 TableParser
164@@ -3211,6 +3212,7 @@
165 use warnings FATAL => 'all';
166 use English qw(-no_match_vars);
167 use Scalar::Util qw(blessed);
168+
169 use constant {
170 PTDEBUG => $ENV{PTDEBUG} || 0,
171 PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
172@@ -3317,21 +3319,6 @@
173 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
174 }
175
176-sub is_cluster_node {
177- my ($self) = @_;
178- return $self->{is_cluster_node} if defined $self->{is_cluster_node};
179-
180- my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
181- PTDEBUG && _d($sql);
182- my $row = $self->{dbh}->selectrow_arrayref($sql);
183- PTDEBUG && _d(defined $row ? @$row : 'undef');
184- $self->{is_cluster_node} = $row && $row->[1]
185- ? ($row->[1] eq 'ON' || $row->[1] eq '1')
186- : 0;
187-
188- return $self->{is_cluster_node};
189-}
190-
191 sub DESTROY {
192 my ($self) = @_;
193 if ( $self->{dbh}
194@@ -3358,6 +3345,104 @@
195 # ###########################################################################
196
197 # ###########################################################################
198+# Percona::XtraDB::Cluster package
199+# This package is a copy without comments from the original. The original
200+# with comments and its test file can be found in the Bazaar repository at,
201+# lib/Percona/XtraDB/Cluster.pm
202+# t/lib/Percona/XtraDB/Cluster.t
203+# See https://launchpad.net/percona-toolkit for more information.
204+# ###########################################################################
205+{
206+
207+package Percona::XtraDB::Cluster;
208+use Mo;
209+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
210+
211+sub is_cluster_node {
212+ my ($self, $cxn) = @_;
213+ return $self->{is_cluster_node}->{$cxn} if defined $self->{is_cluster_node}->{$cxn};
214+
215+ my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
216+ PTDEBUG && _d($sql);
217+ my $row = $cxn->dbh->selectrow_arrayref($sql);
218+ PTDEBUG && _d(defined $row ? @$row : 'undef');
219+ $self->{is_cluster_node}->{$cxn} = $row && $row->[1]
220+ ? ($row->[1] eq 'ON' || $row->[1] eq '1')
221+ : 0;
222+
223+ return $self->{is_cluster_node}->{$cxn};
224+}
225+
226+sub same_cluster {
227+ my ($self, $cxn1, $cxn2) = @_;
228+ return unless $self->is_cluster_node($cxn1) && $self->is_cluster_node($cxn2);
229+ return if $self->is_master_of($cxn1, $cxn2) || $self->is_master_of($cxn2, $cxn1);
230+
231+ my $sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_name'};
232+ PTDEBUG && _d($sql);
233+ my (undef, $row) = $cxn1->dbh->selectrow_array($sql);
234+ my (undef, $cxn2_row) = $cxn2->dbh->selectrow_array($sql);
235+
236+ return unless $row eq $cxn2_row;
237+
238+ $sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_address'};
239+ PTDEBUG && _d($sql);
240+ my (undef, $addr) = $cxn1->dbh->selectrow_array($sql);
241+ my (undef, $cxn2_addr) = $cxn2->dbh->selectrow_array($sql);
242+
243+ return if $addr eq 'gcomm://' && $cxn2_addr eq 'gcomm://';
244+
245+ if ( $addr eq 'gcomm://' ) {
246+ $addr = $self->_find_full_gcomm_addr($cxn1->dbh);
247+ }
248+ elsif ( $cxn2_addr eq 'gcomm://' ) {
249+ $cxn2_addr = $self->_find_full_gcomm_addr($cxn2->dbh);
250+ }
251+
252+ return 1 if lc($addr) eq lc($cxn2_addr);
253+
254+ return 1;
255+}
256+
257+sub is_master_of {
258+ my ($self, $cxn1, $cxn2) = @_;
259+
260+ my $cxn2_dbh = $cxn2->dbh;
261+ my $sql = q{SHOW SLAVE STATUS};
262+ PTDEBUG && _d($sql);
263+ local $cxn2_dbh->{FetchHashKeyName} = 'NAME_lc';
264+ my $slave_status = $cxn2_dbh->selectrow_hashref($sql);
265+ return unless ref($slave_status) eq 'HASH';
266+
267+ my $port = $cxn1->dsn->{P};
268+ return unless $slave_status->{master_port} eq $port;
269+ return 1 if $cxn1->dsn->{h} eq $slave_status->{master_host};
270+
271+ my $host = scalar gethostbyname($cxn1->dsn->{h});
272+ my $master_host = scalar gethostbyname($slave_status->{master_host});
273+ return 1 if $master_host eq $host;
274+ return;
275+}
276+
277+sub _find_full_gcomm_addr {
278+ my ($self, $dbh) = @_;
279+
280+ my $sql = q{SHOW VARIABLES LIKE 'wsrep_provider_options'};
281+ PTDEBUG && _d($sql);
282+ my (undef, $provider_opts) = $dbh->selectrow_array($sql);
283+ my ($prov_addr) = $provider_opts =~ m{\Qgmcast.listen_addr\E\s*=\s*tcp://([^:]+:[0-9]+)\s*;}i;
284+ my $full_gcomm = "gcomm://$prov_addr";
285+ PTDEBUG && _d("gcomm address: ", $full_gcomm);
286+ return $full_gcomm;
287+}
288+
289+1;
290+}
291+# ###########################################################################
292+# End Percona::XtraDB::Cluster package
293+# ###########################################################################
294+
295+# ###########################################################################
296 # Quoter package
297 # This package is a copy without comments from the original. The original
298 # with comments and its test file can be found in the Bazaar repository at,
299@@ -5281,7 +5366,7 @@
300 }
301 my ($dbh) = @args{@required_args};
302 my $o = $self->{OptionParser};
303- my @funcs = qw(CRC32 FNV1A_64 FNV_64 MD5 SHA1);
304+ my @funcs = qw(CRC32 FNV1A_64 FNV_64 MURMUR_HASH MD5 SHA1);
305
306 if ( my $func = $o->get('function') ) {
307 unshift @funcs, $func;
308@@ -5302,7 +5387,7 @@
309 PTDEBUG && _d('Chosen hash func:', $result);
310 return $func;
311 }
312- die $error || 'No hash functions (CRC32, MD5, etc.) are available';
313+ die($error || 'No hash functions (CRC32, MD5, etc.) are available');
314 }
315
316 sub _get_crc_width {
317@@ -8535,6 +8620,9 @@
318 $have_time = sub { return 1; };
319 }
320
321+ # PXC helper class
322+ my $cluster = Percona::XtraDB::Cluster->new();
323+
324 # ########################################################################
325 # If this is not a dry run (--explain was not specified), then we're
326 # going to checksum the tables, so do the necessary preparations and
327@@ -8604,12 +8692,42 @@
328 die $err if $err;
329 }
330
331- if ( $master_cxn->is_cluster_node() && !@$slaves ) {
332- die $master_cxn->name() . " is a cluster node but no other nodes "
333- . "or regular replicas were found. Use --recursion-method=dsn "
334- . "to specify the other nodes in the cluster.\n";
335- }
336-
337+ if ( $cluster->is_cluster_node($master_cxn) ) {
338+ if ( !@$slaves ) {
339+ die $master_cxn->name() . " is a cluster node but no other nodes "
340+ . "or regular replicas were found. Use --recursion-method=dsn "
341+ . "to specify the other nodes in the cluster.\n";
342+ }
343+ else {
344+ my $err = '';
345+ for my $slave (@$slaves) {
346+ if ( $cluster->is_cluster_node($slave)
347+ && !$cluster->same_cluster($slave, $master_cxn) ) {
348+ $err .= $slave->name() . " is a cluster node, but doesn't "
349+ . "belong to the same cluster as " . $master_cxn->name()
350+ . ". This is not currently supported; You can try "
351+ . "using --recursion-method=dsn to specify all nodes "
352+ . "in the slave cluster.\n"
353+ }
354+ }
355+ warn $err if $err;
356+ }
357+ }
358+ elsif ( @$slaves ) {
359+ my $err = '';
360+ for my $slave (@$slaves) {
361+ if ( $cluster->is_cluster_node($slave) ) {
362+ $err .= $slave->name() . " is a cluster node, but "
363+ . $master_cxn->name() . " is not. This is not currently "
364+ . "supported; You can try to specify "
365+ . "all nodes in the cluster with "
366+ . "--recursion-method=dsn if you want them checksummed.\n"
367+ }
368+ }
369+ warn $err if $err;
370+ }
371+
372+
373 if ( $o->get('check-slave-lag') ) {
374 PTDEBUG && _d('Will use --check-slave-lag to check for slave lag');
375 my $cxn = $make_cxn->(
376@@ -8629,7 +8747,8 @@
377 # to appear should be sufficient.
378 @$slave_lag_cxns = grep {
379 my $slave_cxn = $_;
380- if ( $slave_cxn->is_cluster_node() ) {
381+ if ( $cluster->is_cluster_node($slave_cxn)
382+ && $cluster->same_cluster($master_cxn, $slave_cxn) ) {
383 warn "Not checking replica lag on " . $slave_cxn->name()
384 . " because it is a cluster node.\n";
385 0;
386@@ -8844,7 +8963,7 @@
387 . "(db, tbl, chunk, chunk_index,"
388 . " lower_boundary, upper_boundary, this_cnt, this_crc) "
389 . "SELECT"
390- . ($master_cxn->is_cluster_node() ? ' /*!99997*/' : '')
391+ . ($cluster->is_cluster_node($master_cxn) ? ' /*!99997*/' : '')
392 . " ?, ?, ?, ?, ?, ?,";
393 my $past_cols = " COUNT(*), '0'";
394
395
396=== modified file 'lib/Cxn.pm'
397--- lib/Cxn.pm 2012-10-08 18:53:54 +0000
398+++ lib/Cxn.pm 2012-11-08 20:42:20 +0000
399@@ -36,6 +36,7 @@
400 use warnings FATAL => 'all';
401 use English qw(-no_match_vars);
402 use Scalar::Util qw(blessed);
403+
404 use constant {
405 PTDEBUG => $ENV{PTDEBUG} || 0,
406 # Hostnames make testing less accurate. Tests need to see
407@@ -195,21 +196,6 @@
408 return $self->{hostname} || $self->{dsn_name} || 'unknown host';
409 }
410
411-sub is_cluster_node {
412- my ($self) = @_;
413- return $self->{is_cluster_node} if defined $self->{is_cluster_node};
414-
415- my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
416- PTDEBUG && _d($sql);
417- my $row = $self->{dbh}->selectrow_arrayref($sql);
418- PTDEBUG && _d(defined $row ? @$row : 'undef');
419- $self->{is_cluster_node} = $row && $row->[1]
420- ? ($row->[1] eq 'ON' || $row->[1] eq '1')
421- : 0;
422-
423- return $self->{is_cluster_node};
424-}
425-
426 sub DESTROY {
427 my ($self) = @_;
428 if ( $self->{dbh}
429
430=== added directory 'lib/Percona/XtraDB'
431=== added file 'lib/Percona/XtraDB/Cluster.pm'
432--- lib/Percona/XtraDB/Cluster.pm 1970-01-01 00:00:00 +0000
433+++ lib/Percona/XtraDB/Cluster.pm 2012-11-08 20:42:20 +0000
434@@ -0,0 +1,123 @@
435+# This program is copyright 2011 Percona Inc.
436+# Feedback and improvements are welcome.
437+#
438+# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
439+# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
440+# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
441+#
442+# This program is free software; you can redistribute it and/or modify it under
443+# the terms of the GNU General Public License as published by the Free Software
444+# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
445+# systems, you can issue `man perlgpl' or `man perlartistic' to read these
446+# licenses.
447+#
448+# You should have received a copy of the GNU General Public License along with
449+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
450+# Place, Suite 330, Boston, MA 02111-1307 USA.
451+# ###########################################################################
452+# Percona::XtraDB::Cluster package
453+# ###########################################################################
454+{
455+# Package: Percona::XtraDB::Cluster
456+# Percona::XtraDB::Cluster has helper methods to deal with Percona XtraDB Cluster
457+# based servers
458+
459+package Percona::XtraDB::Cluster;
460+use Mo;
461+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
462+
463+sub is_cluster_node {
464+ my ($self, $cxn) = @_;
465+ return $self->{is_cluster_node}->{$cxn} if defined $self->{is_cluster_node}->{$cxn};
466+
467+ my $sql = "SHOW VARIABLES LIKE 'wsrep_on'";
468+ PTDEBUG && _d($sql);
469+ my $row = $cxn->dbh->selectrow_arrayref($sql);
470+ PTDEBUG && _d(defined $row ? @$row : 'undef');
471+ $self->{is_cluster_node}->{$cxn} = $row && $row->[1]
472+ ? ($row->[1] eq 'ON' || $row->[1] eq '1')
473+ : 0;
474+
475+ return $self->{is_cluster_node}->{$cxn};
476+}
477+
478+sub same_cluster {
479+ my ($self, $cxn1, $cxn2) = @_;
480+ return unless $self->is_cluster_node($cxn1) && $self->is_cluster_node($cxn2);
481+ return if $self->is_master_of($cxn1, $cxn2) || $self->is_master_of($cxn2, $cxn1);
482+
483+ my $sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_name'};
484+ PTDEBUG && _d($sql);
485+ my (undef, $row) = $cxn1->dbh->selectrow_array($sql);
486+ my (undef, $cxn2_row) = $cxn2->dbh->selectrow_array($sql);
487+
488+ return unless $row eq $cxn2_row;
489+
490+ # Now it becomes tricky. Ostensibly clusters shouldn't have the
491+ # same name, but tell that to the world.
492+ $sql = q{SHOW VARIABLES LIKE 'wsrep_cluster_address'};
493+ PTDEBUG && _d($sql);
494+ my (undef, $addr) = $cxn1->dbh->selectrow_array($sql);
495+ my (undef, $cxn2_addr) = $cxn2->dbh->selectrow_array($sql);
496+
497+ # If they both have gcomm://, then they are both the first
498+ # node of a cluster, so they can't be in the same one.
499+ return if $addr eq 'gcomm://' && $cxn2_addr eq 'gcomm://';
500+
501+ if ( $addr eq 'gcomm://' ) {
502+ $addr = $self->_find_full_gcomm_addr($cxn1->dbh);
503+ }
504+ elsif ( $cxn2_addr eq 'gcomm://' ) {
505+ $cxn2_addr = $self->_find_full_gcomm_addr($cxn2->dbh);
506+ }
507+
508+ # Meanwhile, if they have the same address, then
509+ # they are definitely part of the same cluster
510+ return 1 if lc($addr) eq lc($cxn2_addr);
511+
512+ # However, this still leaves us with the issue that
513+ # the cluster addresses could look like this:
514+ # node1 -> node2, node2 -> node1,
515+ # or
516+ # node1 -> node2 addr,
517+ # node2 -> node3 addr,
518+ # node3 -> node1 addr,
519+ # TODO No clue what to do here
520+ return 1;
521+}
522+
523+sub is_master_of {
524+ my ($self, $cxn1, $cxn2) = @_;
525+
526+ my $cxn2_dbh = $cxn2->dbh;
527+ my $sql = q{SHOW SLAVE STATUS};
528+ PTDEBUG && _d($sql);
529+ local $cxn2_dbh->{FetchHashKeyName} = 'NAME_lc';
530+ my $slave_status = $cxn2_dbh->selectrow_hashref($sql);
531+ return unless ref($slave_status) eq 'HASH';
532+
533+ my $port = $cxn1->dsn->{P};
534+ return unless $slave_status->{master_port} eq $port;
535+ return 1 if $cxn1->dsn->{h} eq $slave_status->{master_host};
536+
537+ # They might be the same but in different format
538+ my $host = scalar gethostbyname($cxn1->dsn->{h});
539+ my $master_host = scalar gethostbyname($slave_status->{master_host});
540+ return 1 if $master_host eq $host;
541+ return;
542+}
543+
544+sub _find_full_gcomm_addr {
545+ my ($self, $dbh) = @_;
546+
547+ my $sql = q{SHOW VARIABLES LIKE 'wsrep_provider_options'};
548+ PTDEBUG && _d($sql);
549+ my (undef, $provider_opts) = $dbh->selectrow_array($sql);
550+ my ($prov_addr) = $provider_opts =~ m{\Qgmcast.listen_addr\E\s*=\s*tcp://([^:]+:[0-9]+)\s*;}i;
551+ my $full_gcomm = "gcomm://$prov_addr";
552+ PTDEBUG && _d("gcomm address: ", $full_gcomm);
553+ return $full_gcomm;
554+}
555+
556+1;
557+}
558
559=== modified file 'lib/Sandbox.pm'
560--- lib/Sandbox.pm 2012-11-07 07:13:50 +0000
561+++ lib/Sandbox.pm 2012-11-08 20:42:20 +0000
562@@ -39,6 +39,8 @@
563 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
564 use constant PTDEVDEBUG => $ENV{PTDEVDEBUG} || 0;
565
566+use IO::Socket::INET;
567+
568 my $trunk = $ENV{PERCONA_TOOLKIT_BRANCH};
569
570 my %port_for = (
571@@ -74,7 +76,7 @@
572 return if !defined $cmd || !$cmd;
573 my $use = $self->_use_for($server) . " $cmd";
574 PTDEBUG && _d('"Executing', $use, 'on', $server);
575- my $out = `$use 2>&1`;
576+ my $out = `$use`;
577 if ( $? >> 8 ) {
578 die "Failed to execute $cmd on $server: $out";
579 }
580@@ -130,7 +132,7 @@
581 }
582
583 sub load_file {
584- my ( $self, $server, $file, $use_db ) = @_;
585+ my ( $self, $server, $file, $use_db, %args ) = @_;
586 _check_server($server);
587 $file = "$trunk/$file";
588 if ( !-f $file ) {
589@@ -141,11 +143,11 @@
590
591 my $use = $self->_use_for($server) . " $d < $file";
592 PTDEBUG && _d('Loading', $file, 'on', $server, ':', $use);
593- my $out = `$use 2>&1`;
594+ my $out = `$use`;
595 if ( $? >> 8 ) {
596 die "Failed to execute $file on $server: $out";
597 }
598- $self->wait_for_slaves();
599+ $self->wait_for_slaves() unless $args{no_wait};
600 }
601
602 sub _use_for {
603@@ -420,6 +422,85 @@
604 return ($output || '') =~ /1/;
605 }
606
607+sub set_as_slave {
608+ my ($self, $server, $master_server, @extras) = @_;
609+ PTDEBUG && _d("Setting $server as slave of $master_server");
610+ my $master_port = $port_for{$master_server};
611+ my $sql = join ", ", qq{change master to master_host='127.0.0.1'},
612+ qq{master_user='msandbox'},
613+ qq{master_password='msandbox'},
614+ qq{master_port=$master_port},
615+ @extras;
616+ for my $sql_to_run ($sql, "start slave") {
617+ my $out = $self->use($server, qq{-e "$sql_to_run"});
618+ PTDEBUG && _d($out);
619+ }
620+}
621+
622+sub start_sandbox {
623+ my ($self, $mode, $server, $master_server) = @_;
624+ my $port = $port_for{$server};
625+ my $master_port = $master_server ? $port_for{$master_server} : '';
626+ my $out = `$trunk/sandbox/start-sandbox $mode $port $master_port`;
627+ die $out if $CHILD_ERROR;
628+ return $out;
629+}
630+
631+sub stop_sandbox {
632+ my ($self, @sandboxes) = @_;
633+ my @ports = @port_for{@sandboxes};
634+ my $out = `$trunk/sandbox/stop-sandbox @ports`;
635+ die $out if $CHILD_ERROR;
636+ return $out;
637+}
638+
639+sub start_cluster {
640+ my ($self, %args) = @_;
641+ my $cluster_size = $args{cluster_size} || 3;
642+
643+ my $out = '';
644+
645+ my ($node1, @nodes) = map {
646+ my $node_name = "node$_";
647+ $node_name = "_$node_name" while exists $port_for{$node_name};
648+ $port_for{$node_name} = $self->_get_unused_port();
649+ $node_name
650+ } 1..$cluster_size;
651+
652+ local $ENV{CLUSTER_NAME} = $args{cluster_name} if $args{cluster_name};
653+ $self->start_sandbox("cluster", $node1);
654+ for my $node ( @nodes ) {
655+ $self->start_sandbox("cluster", $node, $node1);
656+ }
657+
658+ return ($node1, @nodes);
659+}
660+
661+# Lifted from Nginx::Test on CPAN
662+sub _get_unused_port {
663+ my $port = 50000 + int (rand() * 5000);
664+
665+ while ($port++ < 64000) {
666+ my $sock = IO::Socket::INET->new (
667+ Listen => 5,
668+ LocalAddr => '127.0.0.1',
669+ LocalPort => $port,
670+ Proto => 'tcp',
671+ ReuseAddr => 1
672+ ) or next;
673+
674+ $sock->close;
675+ return $port;
676+ }
677+
678+ die "Cannot find an open port";
679+}
680+
681+sub port_for {
682+ my ($self, $server) = @_;
683+ return $port_for{$server};
684+}
685+
686 1;
687 }
688 # ###########################################################################
689
690=== modified file 't/lib/Cxn.t'
691--- t/lib/Cxn.t 2012-07-23 02:37:39 +0000
692+++ t/lib/Cxn.t 2012-11-08 20:42:20 +0000
693@@ -9,7 +9,8 @@
694 use strict;
695 use warnings FATAL => 'all';
696 use English qw(-no_match_vars);
697-use Test::More tests => 19;
698+use Test::More;
699+use Data::Dumper;
700
701 use Sandbox;
702 use OptionParser;
703@@ -17,8 +18,7 @@
704 use Quoter;
705 use PerconaTest;
706 use Cxn;
707-
708-use Data::Dumper;
709+use VersionParser;
710
711 my $q = new Quoter();
712 my $dp = new DSNParser(opts=>$dsn_opts);
713@@ -248,12 +248,10 @@
714 "Default cxn inherits default connection options"
715 );
716
717-@ARGV = ();
718-$o->get_opts();
719-
720 # #############################################################################
721 # Done.
722 # #############################################################################
723 $master_dbh->disconnect() if $master_dbh;
724 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
725+done_testing;
726 exit;
727
728=== added directory 't/lib/Percona'
729=== added directory 't/lib/Percona/XtraDB'
730=== added file 't/lib/Percona/XtraDB/Cluster.t'
731--- t/lib/Percona/XtraDB/Cluster.t 1970-01-01 00:00:00 +0000
732+++ t/lib/Percona/XtraDB/Cluster.t 2012-11-08 20:42:20 +0000
733@@ -0,0 +1,216 @@
734+#!/usr/bin/perl
735+
736+BEGIN {
737+ die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
738+ unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
739+ unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
740+};
741+
742+use strict;
743+use warnings FATAL => 'all';
744+use English qw(-no_match_vars);
745+use Test::More;
746+use Data::Dumper;
747+
748+# Hostnames make testing less accurate. Tests need to see
749+# that such-and-such happened on specific slave hosts, but
750+# the sandbox servers are all on one host so all slaves have
751+# the same hostname.
752+$ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} = 1;
753+
754+use Sandbox;
755+use OptionParser;
756+use DSNParser;
757+use Quoter;
758+use PerconaTest;
759+use Cxn;
760+use VersionParser;
761+
762+use Percona::XtraDB::Cluster;
763+
764+my $q = new Quoter();
765+my $dp = new DSNParser(opts=>$dsn_opts);
766+my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
767+my $master_dbh = $sb->get_dbh_for('master');
768+
769+my $cluster = Percona::XtraDB::Cluster->new();
770+
771+if ( !$master_dbh ) {
772+ plan skip_all => 'Cannot connect to sandbox master';
773+}
774+
775+my $o = new OptionParser(description => 'Cxn');
776+$o->get_specs("$trunk/bin/pt-table-checksum");
777+$o->get_opts();
778+$dp->prop('set-vars', $o->get('set-vars'));
779+
780+sub make_cxn {
781+ my (%args) = @_;
782+ $o->get_opts();
783+ return new Cxn(
784+ OptionParser => $o,
785+ DSNParser => $dp,
786+ %args,
787+ );
788+}
789+
790+local @ARGV = ();
791+$o->get_opts();
792+
793+diag("Starting master1");
794+$sb->start_sandbox("master", "master1");
795+
796+my ($master_cxn, $slave1_cxn, $master1_cxn)
797+ = map {
798+ my $cxn = make_cxn( dsn_string => $sb->dsn_for($_) );
799+ $cxn->connect();
800+ $cxn;
801+ } qw( master slave1 master1 );
802+
803+for my $cxn ( $master_cxn, $slave1_cxn, $master1_cxn ) {
804+ ok(
805+ !$cluster->is_cluster_node($cxn),
806+ "is_cluster_node works correctly for non-nodes " . $cxn->name
807+ );
808+}
809+
810+ok($cluster->is_master_of($master_cxn, $slave1_cxn), "is_master_of(master, slave1) is true");
811+ok(!$cluster->is_master_of($slave1_cxn, $master_cxn), "is_master_of(slave1, master) is false");
812+
813+my $db_flavor = VersionParser->new($master_dbh)->flavor();
814+SKIP: {
815+ skip "PXC-only test", 17
816+ unless $db_flavor =~ /XtraDB Cluster/;
817+
818+ diag("Starting a 1-node PXC");
819+ my ($node) = $sb->start_cluster(cluster_size => 1);
820+
821+ my $cxn1 = make_cxn( dsn_string => $sb->dsn_for($node) );
822+ $cxn1->connect();
823+ ok(
824+ $cluster->is_cluster_node($cxn1),
825+ "is_cluster_node works correctly for cluster nodes"
826+ );
827+
828+ ok(
829+ !$cluster->is_master_of($master1_cxn, $cxn1),
830+ "->is_master_of works correctly for a server unrelated to a cluster"
831+ );
832+
833+ diag("Setting node as a slave of master1");
834+ $sb->set_as_slave($node, "master1");
835+ ok(
836+ $cluster->is_master_of($master1_cxn, $cxn1),
837+ "->is_master_of works correctly for master -> cluster"
838+ );
839+ ok(
840+ !$cluster->is_master_of($cxn1, $master1_cxn),
841+ "...and the inverse returns the expected result"
842+ );
843+ ok(
844+ !$cluster->same_cluster($master1_cxn, $cxn1),
845+ "->same_cluster works for master -> cluster"
846+ );
847+ diag("Restarting the cluster");
848+ diag($sb->stop_sandbox($node));
849+ ($node) = $sb->start_cluster(cluster_size => 1);
850+ $cxn1 = make_cxn( dsn_string => $sb->dsn_for($node) );
851+ $cxn1->connect();
852+
853+ diag("Setting master1 as a slave of the node");
854+ $sb->set_as_slave("master1", $node);
855+ ok(
856+ $cluster->is_master_of($cxn1, $master1_cxn),
857+ "->is_master_of works correctly for cluster -> master"
858+ );
859+ ok(
860+ !$cluster->is_master_of($master1_cxn, $cxn1),
861+ "...and the inverse returns the expected result"
862+ );
863+
864+ ok(
865+ !$cluster->same_cluster($cxn1, $master1_cxn),
866+ "->same_cluster works for cluster -> master"
867+ );
868+
869+ diag("Starting a 2-node PXC");
870+ my ($node2, $node3) = $sb->start_cluster(cluster_size => 2);
871+
872+ my $cxn2 = make_cxn( dsn_string => $sb->dsn_for($node2) );
873+ $cxn2->connect();
874+ my $cxn3 = make_cxn( dsn_string => $sb->dsn_for($node3) );
875+ $cxn3->connect();
876+ ok(
877+ $cluster->is_cluster_node($cxn2),
878+ "is_cluster_node correctly finds that this node is part of a cluster"
879+ );
880+
881+ ok(
882+ !$cluster->same_cluster($cxn1, $cxn2),
883+ "and same_cluster correctly finds that they don't belong to the same cluster, even when they have the same cluster name"
884+ );
885+
886+ ok(
887+ $cluster->same_cluster($cxn2, $cxn3),
888+ "...but does find that they are in the same cluster, even if one is node1"
889+ );
890+
891+ TODO: {
892+ local $::TODO = "Should detected that (cluster1.node1) (cluster2.node2) come from different clusters, but doesn't";
893+ ok(
894+ !$cluster->same_cluster($cxn1, $cxn3),
895+ "...same_cluster works correctly when they have the same cluster names"
896+ );
897+ }
898+
899+ diag("Making the second cluster a slave of the first");
900+ $sb->set_as_slave($node2, $node);
901+ ok($cluster->is_master_of($cxn1, $cxn2), "is_master_of(cluster1, cluster2) works");
902+
903+ ok(
904+ !$cluster->same_cluster($cxn1, $cxn2),
905+ "...same_cluster works correctly when they are cluster1.node1.master -> cluster2.node1.slave"
906+ );
907+
908+ diag($sb->stop_sandbox($node2, $node3));
909+ diag("Starting a 3-node cluster");
910+ my $node4;
911+ ($node2, $node3, $node4)
912+ = $sb->start_cluster(
913+ cluster_size => 3,
914+ cluster_name => "pt_cxn_test",
915+ );
916+ $cxn2 = make_cxn( dsn_string => $sb->dsn_for($node2) );
917+ $cxn2->connect();
918+ $cxn3 = make_cxn( dsn_string => $sb->dsn_for($node3) );
919+ $cxn3->connect();
920+ my $cxn4 = make_cxn( dsn_string => $sb->dsn_for($node4) );
921+ $cxn4->connect();
922+
923+ ok(
924+ !$cluster->same_cluster($cxn1, $cxn2),
925+ "...same_cluster works correctly when they have different cluster names & the are both gcomm"
926+ );
927+
928+ ok(
929+ !$cluster->same_cluster($cxn1, $cxn3),
930+ "same_cluster detects that (cluster1.node1) (cluster2.node2) come from different clusters if they have different cluster_names"
931+ );
932+
933+ ok(
934+ $cluster->same_cluster($cxn2, $cxn3),
935+ "sanity check: but still finds that nodes in the same cluster belong together"
936+ );
937+
938+ diag($sb->stop_sandbox($node, $node2, $node3, $node4));
939+}
940+
941+diag($sb->stop_sandbox("master1"));
942+
943+# #############################################################################
944+# Done.
945+# #############################################################################
946+$master_dbh->disconnect() if $master_dbh;
947+ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
948+done_testing;
949+exit;
950
951=== added file 't/pt-table-checksum/pxc.t'
952--- t/pt-table-checksum/pxc.t 1970-01-01 00:00:00 +0000
953+++ t/pt-table-checksum/pxc.t 2012-11-08 20:42:20 +0000
954@@ -0,0 +1,242 @@
955+#!/usr/bin/env perl
956+
957+BEGIN {
958+ die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
959+ unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
960+ unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
961+};
962+
963+use strict;
964+use warnings FATAL => 'all';
965+use English qw(-no_match_vars);
966+use Test::More;
967+use Data::Dumper;
968+use File::Spec::Functions;
969+
970+# Hostnames make testing less accurate. Tests need to see
971+# that such-and-such happened on specific slave hosts, but
972+# the sandbox servers are all on one host so all slaves have
973+# the same hostname.
974+$ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} = 1;
975+
976+use PerconaTest;
977+use Sandbox;
978+
979+require "$trunk/bin/pt-table-checksum";
980+# Do this after requiring ptc, since it uses Mo
981+require VersionParser;
982+
983+my $dp = new DSNParser(opts=>$dsn_opts);
984+my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
985+my $master_dbh = $sb->get_dbh_for('master');
986+
987+my $db_flavor = VersionParser->new($master_dbh)->flavor();
988+
989+if ( !$master_dbh ) {
990+ plan skip_all => 'Cannot connect to sandbox master';
991+}
992+elsif ( $db_flavor !~ /XtraDB Cluster/ ) {
993+ plan skip_all => "PXC tests";
994+}
995+
996+# The sandbox servers run with lock_wait_timeout=3 and it's not dynamic
997+# so we need to specify --lock-wait-timeout=3 else the tool will die.
998+my $master_dsn = 'h=127.1,P=12345,u=msandbox,p=msandbox';
999+my @args = ($master_dsn, qw(--lock-wait-timeout 3));
1000+my $output;
1001+my $exit_status;
1002+my $sample = "t/pt-table-checksum/samples/";
1003+
1004+# #############################################################################
1005+# pt-table-checksum v2.1.4 doesn't detect diffs on Percona XtraDB Cluster nodes
1006+# https://bugs.launchpad.net/percona-toolkit/+bug/1062563
1007+# #############################################################################
1008+
1009+sub make_dbh_differ {
1010+ my ($dbh, @vals) = @_;
1011+ @vals = (@vals ? @vals : 1);
1012+ # Make them differ...
1013+ $dbh->do("DROP DATABASE IF EXISTS bug_1062563");
1014+ $dbh->do("CREATE DATABASE bug_1062563");
1015+ $dbh->do("CREATE TABLE bug_1062563.ptc_pxc (i int)");
1016+
1017+ # Now make this node different from the rest
1018+ $dbh->do("set sql_log_bin=0");
1019+ $dbh->do("INSERT INTO bug_1062563.ptc_pxc (i) VALUES ($_)") for @vals;
1020+ $dbh->do("set sql_log_bin=1");
1021+}
1022+
1023+diag("Creating a 5-node PXC cluster...");
1024+my @nodes = $sb->start_cluster(cluster_size => 5);
1025+diag("Nodes: ", Dumper( { map { $_ => $sb->port_for($_) } @nodes } ));
1026+
1027+my $node2 = $nodes[1];
1028+my $node2_dbh = $sb->get_dbh_for($node2);
1029+
1030+my $node2_slave = "master3";
1031+
1032+diag("Creating a slave for $node2...");
1033+{
1034+ local $ENV{BINLOG_FORMAT} = 'ROW';
1035+ diag($sb->start_sandbox("slave", $node2_slave, $node2));
1036+}
1037+my $node_slave_dbh = $sb->get_dbh_for($node2_slave);
1038+
1039+make_dbh_differ($node2_dbh);
1040+
1041+# And make its slave differ as well
1042+PerconaTest::wait_for_table($sb->get_dbh_for($nodes[-1]), "bug_1062563.ptc_pxc");
1043+PerconaTest::wait_for_table($node_slave_dbh, "bug_1062563.ptc_pxc");
1044+$node_slave_dbh->do("INSERT INTO bug_1062563.ptc_pxc (i) VALUES ($_)") for 3, 4;
1045+
1046+my $dsns_table_sql = catfile(qw(t lib samples MasterSlave dsn_table.sql));
1047+$sb->load_file($node2, $dsns_table_sql, undef, no_wait => 1);
1048+$node2_dbh->do("DELETE FROM dsn_t.dsns"); # Delete 12346
1049+my $sth = $node2_dbh->prepare("INSERT INTO dsn_t.dsns VALUES (null, null, ?)");
1050+for my $dsn ( map { $sb->dsn_for($_) } @nodes[0,2..$#nodes], $node2_slave ) {
1051+ $sth->execute($dsn);
1052+}
1053+
1054+my $node2_dsn = $sb->dsn_for($node2);
1055+$output = output(
1056+ sub { pt_table_checksum::main(
1057+ $node2_dsn, qw(--lock-wait-timeout 3),
1058+ qw(-d bug_1062563),
1059+ '--recursion-method', "dsn=D=dsn_t,t=dsns"
1060+ ) },
1061+ stderr => 1,
1062+);
1063+
1064+is(
1065+ PerconaTest::count_checksum_results($output, 'diffs'),
1066+ 1,
1067+ "Bug 1062563: Detects diffs between PXC nodes"
1068+) or diag($output);
1069+
1070+my @cluster_nodes = $output =~ /(because it is a cluster node)/g;
1071+is(
1072+ scalar(@cluster_nodes),
1073+ 4,
1074+ "Skips all the cluster nodes in the dsns table"
1075+) or diag($output);
1076+
1077+# Now try with just the slave
1078+
1079+$node2_dbh->do("DELETE FROM dsn_t.dsns");
1080+$sth->execute($sb->dsn_for($node2_slave));
1081+
1082+$output = output(
1083+ sub { pt_table_checksum::main(
1084+ $node2_dsn, qw(--lock-wait-timeout 3),
1085+ qw(--chunk-size 1),
1086+ qw(-d bug_1062563),
1087+ '--recursion-method', "dsn=D=dsn_t,t=dsns"
1088+ ) },
1089+ stderr => 1,
1090+);
1091+
1092+is(
1093+ PerconaTest::count_checksum_results($output, 'diffs'),
1094+ 1,
1095+ "Bug 1062563: Detects diffs on slaves where the master is a PXC node"
1096+) or diag($output);
1097+
1098+$sth->finish();
1099+diag("Stopping the PXC cluster and the slave...");
1100+$sb->stop_sandbox($node2_slave, @nodes);
1101+
1102+# Now checking that cluster -> cluster works
1103+
1104+diag("Creating two 3-node clusters...");
1105+my @cluster1 = $sb->start_cluster(cluster_size => 3, cluster_name => "pt_test_cluster_1");
1106+my @cluster2 = $sb->start_cluster(cluster_size => 3, cluster_name => "pt_test_cluster_2");
1107+diag("Cluster 1: ", Dumper( { map { $_ => $sb->port_for($_) } @cluster1 } ));
1108+diag("Cluster 2: ", Dumper( { map { $_ => $sb->port_for($_) } @cluster2 } ));
1109+
1110+$sb->set_as_slave($cluster2[0], $cluster1[0]);
1111+
1112+my $cluster1_dbh = $sb->get_dbh_for($cluster1[0]);
1113+my $cluster2_dbh = $sb->get_dbh_for($cluster2[0]);
1114+make_dbh_differ($cluster1_dbh);
1115+
1116+# And make its slave differ as well
1117+PerconaTest::wait_for_table($sb->get_dbh_for($cluster2[-1]), "bug_1062563.ptc_pxc");
1118+PerconaTest::wait_for_table($sb->get_dbh_for($cluster1[-1]), "bug_1062563.ptc_pxc");
1119+PerconaTest::wait_for_table($cluster2_dbh, "bug_1062563.ptc_pxc");
1120+$cluster2_dbh->do("INSERT INTO bug_1062563.ptc_pxc (i) VALUES ($_)") for 3, 4;
1121+
1122+$dsns_table_sql = catfile(qw(t lib samples MasterSlave dsn_table.sql));
1123+$sb->load_file($cluster1[0], $dsns_table_sql, undef, no_wait => 1);
1124+$cluster1_dbh->do("DELETE FROM dsn_t.dsns"); # Delete 12346
1125+$sth = $cluster1_dbh->prepare("INSERT INTO dsn_t.dsns VALUES (null, null, ?)");
1126+for my $dsn ( map { $sb->dsn_for($_) } @cluster1[1..$#cluster1], $cluster2[0] ) {
1127+ $sth->execute($dsn);
1128+}
1129+$sth->finish();
1130+
1131+my $cluster1_dsn = $sb->dsn_for($cluster1[0]);
1132+$output = output(
1133+ sub { pt_table_checksum::main(
1134+ $cluster1_dsn, qw(--lock-wait-timeout 3),
1135+ qw(-d bug_1062563),
1136+ '--recursion-method', "dsn=D=dsn_t,t=dsns"
1137+ ) },
1138+ stderr => 1,
1139+);
1140+
1141+is(
1142+ PerconaTest::count_checksum_results($output, 'diffs'),
1143+ 1,
1144+ "Bug 1062563: Detects diffs between PXC nodes when cluster -> cluster"
1145+) or diag($output);
1146+
1147+like(
1148+ $output,
1149+ qr/is a cluster node, but doesn't belong to the same cluster as/, #'
1150+ "Shows a warning when cluster -> cluster"
1151+) or diag($output);
1152+
1153+diag("Starting master1...");
1154+$sb->start_sandbox("master", "master1");
1155+diag("Setting it as master of a node in the first cluster");
1156+$sb->set_as_slave($cluster1[0], "master1");
1157+
1158+my $master1_dbh = $sb->get_dbh_for("master1");
1159+make_dbh_differ($master1_dbh, 10..50);
1160+
1161+my $master1_dsn = $sb->dsn_for("master1");
1162+$output = output(
1163+ sub { pt_table_checksum::main(
1164+ $master1_dsn, qw(--lock-wait-timeout 3),
1165+ qw(-d bug_1062563),
1166+ ) },
1167+ stderr => 1,
1168+);
1169+
1170+is(
1171+ PerconaTest::count_checksum_results($output, 'diffs'),
1172+ 1,
1173+ "Bug 1062563: Detects diffs when master -> cluster"
1174+) or diag($output);
1175+
1176+is(
1177+ PerconaTest::count_checksum_results($output, 'rows'),
1178+ 41,
1179+ "Bug 1062563: Correct number of rows for master -> cluster"
1180+) or diag($output);
1181+
1182+like(
1183+ $output,
1184+ qr/is a cluster node, but .*? is not. This is not currently supported/,
1185+ "Shows a warning when master -> cluster"
1186+) or diag($output);
1187+
1188+diag("Stopping both clusters and master1...");
1189+$sb->stop_sandbox(@cluster1, @cluster2, "master1");
1190+
1191+# #############################################################################
1192+# Done.
1193+# #############################################################################
1194+$sb->wipe_clean($master_dbh);
1195+ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
1196+done_testing;

Subscribers

People subscribed via source and target branches