diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index 935e235f..e5d5c1f2 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -2485,6 +2485,9 @@ sub get_slaves { dsn_table_dsn => $dsn_table_dsn, ); } + elsif ( $method =~ m/none/i ) { + PTDEBUG && _d('Not getting to slaves'); + } else { die "Invalid --recursion-method: $method. Valid values are: " . "dsn=DSN, hosts, or processlist.\n"; @@ -2499,6 +2502,11 @@ sub recurse_to_slaves { my $dp = $args->{dsn_parser}; my $dsn = $args->{dsn}; + if ( lc($args->{method} || '') eq 'none' ) { + PTDEBUG && _d('Not recursing to slaves'); + return; + } + my $dbh; eval { $dbh = $args->{dbh} || $dp->get_dbh( @@ -4080,12 +4088,15 @@ sub _next_boundaries { return 1; # continue nibbling } + + if ( $self->identical_boundaries($self->{lower}, $self->{next_lower}) ) { PTDEBUG && _d('Infinite loop detected'); my $tbl = $self->{tbl}; my $index = $tbl->{tbl_struct}->{keys}->{$self->{index}}; my $n_cols = scalar @{$index->{cols}}; my $chunkno = $self->{nibbleno}; + die "Possible infinite loop detected! " . "The lower boundary for chunk $chunkno is " . "<" . join(', ', @{$self->{lower}}) . "> and the lower " @@ -4112,6 +4123,7 @@ sub _next_boundaries { } } + PTDEBUG && _d($self->{ub_sth}->{Statement}, 'params:', join(', ', @{$self->{lower}}), $self->{limit}); $self->{ub_sth}->execute(@{$self->{lower}}, $self->{limit}); diff --git a/lib/MasterSlave.pm b/lib/MasterSlave.pm index 89167592..054e4a27 100644 --- a/lib/MasterSlave.pm +++ b/lib/MasterSlave.pm @@ -76,6 +76,10 @@ sub get_slaves { dsn_table_dsn => $dsn_table_dsn, ); } + elsif ( $method =~ m/none/i ) { + # https://bugs.launchpad.net/percona-toolkit/+bug/987694 + PTDEBUG && _d('Not getting to slaves'); + } else { die "Invalid --recursion-method: $method. Valid values are: " . "dsn=DSN, hosts, or processlist.\n"; @@ -110,6 +114,12 @@ sub recurse_to_slaves { my $dp = $args->{dsn_parser}; my $dsn = $args->{dsn}; + if ( lc($args->{method} || '') eq 'none' ) { + # https://bugs.launchpad.net/percona-toolkit/+bug/987694 + PTDEBUG && _d('Not recursing to slaves'); + return; + } + my $dbh; eval { $dbh = $args->{dbh} || $dp->get_dbh( diff --git a/t/lib/MasterSlave.t b/t/lib/MasterSlave.t index 72fce9fa..fb4c00d4 100644 --- a/t/lib/MasterSlave.t +++ b/t/lib/MasterSlave.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 47; +use Test::More tests => 50; use MasterSlave; use DSNParser; @@ -119,6 +119,96 @@ SKIP: { [], "get_slaves() by processlist" ); + + # ########################################################################## + # --recursion-method=none + # https://bugs.launchpad.net/percona-toolkit/+bug/987694 + # ########################################################################## + + # Create percona.checksums to make the privs happy. + diag(`/tmp/12345/use -e "create database if not exists percona"`); + diag(`/tmp/12345/use -e "create table if not exists percona.checksums (id int)"`); + + # Create a read-only checksum user that can't SHOW SLAVES HOSTS or much else. + diag(`/tmp/12345/use -u root < $trunk/t/lib/samples/ro-checksum-user.sql`); + + my $ro_dbh = DBI->connect( + "DBI:mysql:;host=127.0.0.1;port=12345", 'ro_checksum_user', 'msandbox', + { PrintError => 0, RaiseError => 1 }); + my $ro_dsn = { + h => '127.1', + P => '12345', + u => 'ro_checksum_user', + p => 'ro_checksum_user', + }; + + @ARGV = ('--recursion-method', 'hosts'); + $o->get_opts(); + throws_ok( + sub { + $slaves = $ms->get_slaves( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + dbh => $ro_dbh, + dsn => $ro_dsn, + make_cxn => sub { + my $cxn = new Cxn( + @_, + DSNParser => $dp, + OptionParser => $o, + ); + $cxn->connect(); + return $cxn; + }, + ); + }, + qr/Access denied/, + "Can't SHOW SLAVE HOSTS without privs (bug 987694)" + ); + + @ARGV = ('--recursion-method', 'none'); + $o->get_opts(); + $slaves = $ms->get_slaves( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + dbh => $ro_dbh, + dsn => $ro_dsn, + make_cxn => sub { + my $cxn = new Cxn( + @_, + DSNParser => $dp, + OptionParser => $o, + ); + $cxn->connect(); + return $cxn; + }, + ); + is_deeply( + $slaves, + [], + "No privs needed for --recursion-method=none (bug 987694)" + ); + + my $recursed = 0; + $ms->recurse_to_slaves( + { dsn_parser => $dp, + dbh => $ro_dbh, + dsn => $ro_dsn, + recurse => 2, + callback => sub { $recursed++ }, + method => 'none', + }); + is( + $recursed, + 0, + "recurse_to_slaves() doesn't recurse if method=none" + ); + + $ro_dbh->disconnect(); + diag(`/tmp/12345/use -u root -e "drop user 'ro_checksum_user'\@'%'"`); + } # ############################################################################# diff --git a/t/lib/samples/ro-checksum-user.sql b/t/lib/samples/ro-checksum-user.sql new file mode 100644 index 00000000..f0f73b20 --- /dev/null +++ b/t/lib/samples/ro-checksum-user.sql @@ -0,0 +1,3 @@ +CREATE USER 'ro_checksum_user'@'%' IDENTIFIED BY 'msandbox'; +GRANT SELECT ON sakila.* TO 'ro_checksum_user'@'%'; +GRANT SELECT, INSERT, UPDATE, DELETE ON percona.checksums TO 'ro_checksum_user'@'%'; diff --git a/t/pt-table-checksum/privs.t b/t/pt-table-checksum/privs.t new file mode 100644 index 00000000..740c296e --- /dev/null +++ b/t/pt-table-checksum/privs.t @@ -0,0 +1,98 @@ +#!/usr/bin/env perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +# Hostnames make testing less accurate. Tests need to see +# that such-and-such happened on specific slave hosts, but +# the sandbox servers are all on one host so all slaves have +# the same hostname. +$ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} = 1; + +use Data::Dumper; +use PerconaTest; +use Sandbox; + +# Fix @INC because pt-table-checksum uses subclass OobNibbleIterator. +shift @INC; # our unshift (above) +shift @INC; # PerconaTest's unshift +require "$trunk/bin/pt-table-checksum"; + +my $dp = new DSNParser(opts=>$dsn_opts); +my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); +my $master_dbh = $sb->get_dbh_for('master'); +my $slave_dbh = $sb->get_dbh_for('slave1'); + +if ( !$master_dbh ) { + plan skip_all => 'Cannot connect to sandbox master'; +} +elsif ( !$slave_dbh ) { + plan skip_all => 'Cannot connect to sandbox slave1'; +} +elsif ( !@{$master_dbh->selectall_arrayref('show databases like "sakila"')} ) { + plan skip_all => 'sakila database is not loaded'; +} +else { + plan tests => 2; +} + +# The sandbox servers run with lock_wait_timeout=3 and it's not dynamic +# so we need to specify --lock-wait-timeout=3 else the tool will die. +my $master_dsn = 'h=127.1,P=12345'; +my @args = (qw(--lock-wait-timeout 3)); +my $row; +my $output; +my $exit_status; +my $sample = "t/pt-table-checksum/samples/"; + +# ############################################################################ +# --recursion-method=none to avoid SHOW SLAVE HOSTS +# https://bugs.launchpad.net/percona-toolkit/+bug/987694 +# ############################################################################ + +# Create percona.checksums because ro_checksum_user doesn't have the privs. +pt_table_checksum::main(@args, + "$master_dsn,u=msandbox,p=msandbox", + qw(-t sakila.country --quiet --quiet)); + +diag(`/tmp/12345/use -u root < $trunk/t/lib/samples/ro-checksum-user.sql`); +PerconaTest::wait_for_table($slave_dbh, "mysql.tables_priv", "user='ro_checksum_user'"); + +$output = output( + sub { $exit_status = pt_table_checksum::main(@args, + "$master_dsn,u=ro_checksum_user,p=msandbox", + # Comment out this line and the tests fail because ro_checksum_user + # doesn't have privs to SHOW SLAVE HOSTS. This proves that + # --recursion-method none is working. + qw(--recursion-method none) + ) }, + stderr => 1, +); + +is( + $exit_status, + 0, + "Read-only user (bug 987694): 0 exit" +); + +like( + $output, + qr/ sakila.store$/m, + "Read-only user (bug 987694): checksummed rows" +); + +diag(`/tmp/12345/use -u root -e "drop user 'ro_checksum_user'\@'%'"`); + +# ############################################################################# +# Done. +# ############################################################################# +$sb->wipe_clean($master_dbh); +exit;