diff --git a/bin/pt-archiver b/bin/pt-archiver index 8ad612a1..84d508b1 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -3078,8 +3078,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -3089,28 +3112,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -3120,31 +3142,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -3179,11 +3218,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -3195,25 +3234,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -3716,13 +3744,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -4054,7 +4085,11 @@ sub main { my $dsn_defaults = $dp->parse_options($o); my $dsn = $dp->parse($o->get('check-slave-lag'), $dsn_defaults); $lag_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 }); - $ms = new MasterSlave(); + $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + ); } # ######################################################################## diff --git a/bin/pt-heartbeat b/bin/pt-heartbeat index 98625884..dc14eff0 100755 --- a/bin/pt-heartbeat +++ b/bin/pt-heartbeat @@ -24,8 +24,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -35,28 +58,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -66,31 +88,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -125,11 +164,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -141,25 +180,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -662,13 +690,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -1994,7 +2025,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -2004,15 +2035,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -2024,12 +2057,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -3282,6 +3315,13 @@ sub main { } } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); # ######################################################################## @@ -3407,7 +3447,11 @@ sub main { my $master_server_id = $o->get('master-server-id'); if ( !$master_server_id ) { eval { - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + ); my $master_dsn = $ms->get_master_dsn($dbh, $dsn, $dp) or die "This server is not a slave"; my $master_dbh = $dp->get_dbh($dp->get_cxn_params($master_dsn), @@ -3734,19 +3778,20 @@ sub check_delay { # Collect a list of connections to the slaves. if ( $o->get('recurse') ) { PTDEBUG && _d('Recursing to slaves'); - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => "Quoter", + ); $ms->recurse_to_slaves( { dbh => $dbh, dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), callback => sub { my ( $dsn, $dbh, $level ) = @_; push @dbhs, $dbh; PTDEBUG && _d("Found slave", $dp->as_string($dsn)); push @sths, [ $dsn, $dbh->prepare($sql) ]; }, - method => $o->get('recursion-method'), }, ); } @@ -4235,7 +4280,7 @@ This currently works only with MySQL. See L<"--recursion-method">. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method used to find slaves. diff --git a/bin/pt-kill b/bin/pt-kill index f725a67e..2a501bd6 100755 --- a/bin/pt-kill +++ b/bin/pt-kill @@ -3345,8 +3345,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -3356,28 +3379,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -3387,31 +3409,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -3446,11 +3485,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -3462,25 +3501,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -3983,13 +4011,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -4802,10 +4833,6 @@ my $o; sub main { @ARGV = @_; # set global ARGV for this package - my $ms = new MasterSlave(); - my $pl = new Processlist(MasterSlave => $ms); - my $qr = new QueryRewriter(); - # ######################################################################## # Get configuration information. # ######################################################################## @@ -4880,6 +4907,14 @@ sub main { # ######################################################################## # Make input sub that will either get processlist from MySQL or a file. # ######################################################################## + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => "Quoter", + ); + my $pl = new Processlist(MasterSlave => $ms); + my $qr = new QueryRewriter(); + my $cxn; my $dbh; # $cxn->dbh my $get_proclist; # callback to SHOW PROCESSLIST diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index 749660e4..d3310425 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -1935,7 +1935,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -1945,15 +1945,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -1965,12 +1967,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -3333,6 +3335,7 @@ sub new { dsn_name => $dp->as_string($dsn, [qw(h P S)]), hostname => '', set => $args{set}, + NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1, dbh_set => 0, OptionParser => $o, DSNParser => $dp, @@ -3370,7 +3373,10 @@ sub set_dbh { PTDEBUG && _d($dbh, 'Setting dbh'); - $dbh->{FetchHashKeyName} = 'NAME_lc'; + if ( !exists $self->{NAME_lc} + || (defined $self->{NAME_lc} && $self->{NAME_lc}) ) { + $dbh->{FetchHashKeyName} = 'NAME_lc'; + } my $sql = 'SELECT @@hostname, @@server_id'; PTDEBUG && _d($dbh, $sql); @@ -3446,8 +3452,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -3457,28 +3486,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -3488,31 +3516,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -3547,11 +3592,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -3563,25 +3608,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -4084,13 +4118,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -5909,6 +5946,13 @@ sub main { } } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); if ( $o->get('quiet') ) { @@ -6006,13 +6050,15 @@ sub main { # ##################################################################### # Find and connect to slaves. # ##################################################################### - my $ms = new MasterSlave(); - $slaves = $ms->get_slaves( - dbh => $cxn->dbh(), - dsn => $cxn->dsn(), + my $ms = new MasterSlave( OptionParser => $o, DSNParser => $dp, Quoter => $q, + ); + + $slaves = $ms->get_slaves( + dbh => $cxn->dbh(), + dsn => $cxn->dsn(), make_cxn => sub { return $make_cxn->(@_, prev_dsn => $cxn->dsn()); }, @@ -8368,7 +8414,7 @@ Default is infinite. See also L<"--recursion-method">. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method for discovering replicas. Possible methods are: diff --git a/bin/pt-query-digest b/bin/pt-query-digest index 15155889..f2e66307 100755 --- a/bin/pt-query-digest +++ b/bin/pt-query-digest @@ -10339,8 +10339,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -10350,28 +10373,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -10381,31 +10403,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -10440,11 +10479,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -10456,25 +10495,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -10977,13 +11005,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -12195,7 +12226,11 @@ sub main { { # event my $misc; if ( my $ps_dsn = $o->get('processlist') ) { - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + ); my $pl = new Processlist( interval => $o->get('interval') * 1_000_000, MasterSlave => $ms diff --git a/bin/pt-slave-find b/bin/pt-slave-find index b62aebf6..b7ec1577 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -1741,7 +1741,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -1751,15 +1751,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -1771,12 +1773,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -1870,8 +1872,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -1881,28 +1906,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -1912,31 +1936,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -1971,11 +2012,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -1987,25 +2028,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -2508,13 +2538,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -3330,6 +3363,13 @@ sub main { $o->save_error("Missing or invalid master host"); } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); # ######################################################################## @@ -3355,14 +3395,15 @@ sub main { # Despite the name, recursing to slaves actually begins at the specified # server, so the named server may also be included. - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => "Quoter", + ); $ms->recurse_to_slaves( - { dbh => $dbh, - dsn => $master_dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $master_dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; if ( !$parent ) { $root = $dsn; @@ -3710,7 +3751,7 @@ See L<"--recursion-method">. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method used to find slaves. diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index 0fc1de28..b0f78761 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -2054,7 +2054,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -2064,15 +2064,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -2084,12 +2086,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -2183,8 +2185,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -2194,28 +2219,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -2225,31 +2249,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -2284,11 +2325,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -2300,25 +2341,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -2821,13 +2851,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -3113,6 +3146,13 @@ sub main { } } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); # ######################################################################## @@ -3178,13 +3218,15 @@ sub main { # Despite the name, recursing to slaves actually begins at the specified # server, so the named server may also be watched, if it's a slave. - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + ); $ms->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse') || 0, - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level ) = @_; # Test whether we want to watch this server. eval { @@ -3206,7 +3248,6 @@ sub main { my ( $dsn, $dbh, $level ) = @_; print STDERR "Skipping ", $dp->as_string($dsn), "\n"; }, - method => $o->get('recursion-method'), } ); @@ -3792,7 +3833,7 @@ password. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method used to find slaves. diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index d0dba765..05c94962 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -266,7 +266,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -276,15 +276,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -296,12 +298,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -1901,6 +1903,7 @@ sub new { dsn_name => $dp->as_string($dsn, [qw(h P S)]), hostname => '', set => $args{set}, + NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1, dbh_set => 0, OptionParser => $o, DSNParser => $dp, @@ -1938,7 +1941,10 @@ sub set_dbh { PTDEBUG && _d($dbh, 'Setting dbh'); - $dbh->{FetchHashKeyName} = 'NAME_lc'; + if ( !exists $self->{NAME_lc} + || (defined $self->{NAME_lc} && $self->{NAME_lc}) ) { + $dbh->{FetchHashKeyName} = 'NAME_lc'; + } my $sql = 'SELECT @@hostname, @@server_id'; PTDEBUG && _d($dbh, $sql); @@ -3030,8 +3036,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -3041,28 +3070,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -3072,31 +3100,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -3131,11 +3176,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -3147,25 +3192,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -3668,13 +3702,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -6856,6 +6893,13 @@ sub main { } } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); # ######################################################################## @@ -7006,7 +7050,11 @@ sub main { my $q = new Quoter(); my $tp = new TableParser(Quoter => $q); my $rc = new RowChecksum(Quoter=> $q, OptionParser => $o); - my $ms = new MasterSlave(); + my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, + ); my $slaves; # all slaves (that we can find) my $slave_lag_cxns; # slaves whose lag we'll check @@ -7026,12 +7074,9 @@ sub main { # Find and connect to slaves. # ##################################################################### $slaves = $ms->get_slaves( - dbh => $master_dbh, - dsn => $master_dsn, - OptionParser => $o, - DSNParser => $dp, - Quoter => $q, - make_cxn => sub { + dbh => $master_dbh, + dsn => $master_dsn, + make_cxn => sub { return $make_cxn->(@_, prev_dsn => $master_cxn->dsn()); }, ); @@ -9347,7 +9392,7 @@ Default is infinite. See also L<"--recursion-method">. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method for discovering replicas. Possible methods are: diff --git a/bin/pt-table-sync b/bin/pt-table-sync index 688dda07..fc67a6c4 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -1860,7 +1860,7 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); my ($sql_mode) = eval { $dbh->selectrow_array($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error getting the current SQL_MODE: $EVAL_ERROR"; } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -1870,15 +1870,17 @@ sub get_dbh { PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting SQL_QUOTE_SHOW_CREATE, SQL_MODE" + . ($sql_mode ? " and $sql_mode" : '') + . ": $EVAL_ERROR"; } - if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { - $sql = "/*!40101 SET NAMES $charset*/"; + if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { + $sql = qq{/*!40101 SET NAMES "$charset"*/}; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting NAMES to $charset: $EVAL_ERROR"; } PTDEBUG && _d('Enabling charset for STDOUT'); if ( $charset eq 'utf8' ) { @@ -1890,12 +1892,12 @@ sub get_dbh { } } - if ( $self->prop('set-vars') ) { - $sql = "SET " . $self->prop('set-vars'); + if ( my $var = $self->prop('set-vars') ) { + $sql = "SET $var"; PTDEBUG && _d($dbh, ':', $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { - die $EVAL_ERROR; + die "Error setting $var: $EVAL_ERROR"; } } } @@ -6454,8 +6456,31 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -6465,28 +6490,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -6496,31 +6520,48 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + return $o->get('recursion-method'); + } +} + sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { PTDEBUG && _d('Not recursing to slaves'); return; } @@ -6555,11 +6596,11 @@ sub recurse_to_slaves { $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -6571,25 +6612,14 @@ sub recurse_to_slaves { } sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - @methods = grep { $_ ne $method } @methods; - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -7092,13 +7122,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { @@ -8320,6 +8353,13 @@ sub main { return 0; } + eval { + MasterSlave::check_recursion_method($o->get('recursion-method')); + }; + if ( $EVAL_ERROR ) { + $o->save_error("Invalid --recursion-method: $EVAL_ERROR") + } + $o->usage_or_errors(); # ######################################################################## @@ -8338,7 +8378,7 @@ sub main { # Do the work. # ######################################################################## my $tp = new TableParser( Quoter => $q ); - my $ms = new MasterSlave(); + my $ms = new MasterSlave(OptionParser=>$o,DSNParser=>$dp,Quoter=>$q); my $du = new MySQLDump( cache => 0 ); my $rt = new Retry(); my $chunker = new TableChunker( Quoter => $q, TableParser => $tp ); @@ -8736,11 +8776,10 @@ sub sync_via_replication { # then sync them. else { $ms->recurse_to_slaves( - { dbh => $src->{dbh}, - dsn => $src->{dsn}, - dsn_parser => $dp, - recurse => 1, - callback => sub { + { dbh => $src->{dbh}, + dsn => $src->{dsn}, + recurse => 1, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; my $all_diffs = $checksum->find_replication_differences( $dbh, $o->get('replicate')); @@ -8809,7 +8848,6 @@ sub sync_via_replication { return; }, # recurse_to_slaves() callback - method => $o->get('recursion-method'), }, ); } # DSN is master @@ -10767,7 +10805,7 @@ yourself if you want to sync the tables manually. =item --recursion-method -type: string +type: array; default: processlist,hosts Preferred recursion method used to find slaves. diff --git a/lib/MasterSlave.pm b/lib/MasterSlave.pm index d343e52c..a60f63c4 100644 --- a/lib/MasterSlave.pm +++ b/lib/MasterSlave.pm @@ -1,4 +1,4 @@ -# This program is copyright 2007-2011 Baron Schwartz, 2011 Percona Inc. +# This program is copyright 2007-2011 Baron Schwartz, 2011-2012 Percona Inc. # Feedback and improvements are welcome. # # THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED @@ -27,8 +27,33 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +# Sub: check_recursion_method +# Check that the arrayref of recursion methods passed in is valid +sub check_recursion_method { + my ($methods) = @_; + + if ( @$methods != 1 ) { + if ( grep({ !m/processlist|hosts/i } @$methods) + && $methods->[0] !~ /^dsn=/i ) + { + die "Invalid combination of recursion methods: " + . join(", ", map { defined($_) ? $_ : 'undef' } @$methods) . ". " + . "Only hosts and processlist may be combined.\n" + } + } + else { + my ($method) = @$methods; + die "Invalid recursion method: " . ( $method || 'undef' ) + unless $method && $method =~ m/^(?:processlist$|hosts$|none$|dsn=)/i; + } +} + sub new { my ( $class, %args ) = @_; + my @required_args = qw(OptionParser DSNParser Quoter); + foreach my $arg ( @required_args ) { + die "I need a $arg argument" unless $args{$arg}; + } my $self = { %args, replication_thread => {}, @@ -38,28 +63,27 @@ sub new { sub get_slaves { my ($self, %args) = @_; - my @required_args = qw(make_cxn OptionParser DSNParser Quoter); + my @required_args = qw(make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($make_cxn, $o, $dp) = @args{@required_args}; + my ($make_cxn) = @args{@required_args}; - my $slaves = []; - my $method = $o->get('recursion-method'); - PTDEBUG && _d('Slave recursion method:', $method); - if ( !$method || $method =~ m/processlist|hosts/i ) { + my $slaves = []; + my $dp = $self->{DSNParser}; + my $methods = $self->_resolve_recursion_methods($args{dsn}); + + if ( grep { m/processlist|hosts/i } @$methods ) { my @required_args = qw(dbh dsn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my ($dbh, $dsn) = @args{@required_args}; + $self->recurse_to_slaves( - { dbh => $dbh, - dsn => $dsn, - dsn_parser => $dp, - recurse => $o->get('recurse'), - method => $o->get('recursion-method'), - callback => sub { + { dbh => $dbh, + dsn => $dsn, + callback => sub { my ( $dsn, $dbh, $level, $parent ) = @_; return unless $level; PTDEBUG && _d('Found slave:', $dp->as_string($dsn)); @@ -69,25 +93,41 @@ sub get_slaves { } ); } - elsif ( $method =~ m/^dsn=/i ) { - my ($dsn_table_dsn) = $method =~ m/^dsn=(.+)/i; + elsif ( $methods->[0] =~ m/^dsn=/i ) { + (my $dsn_table_dsn = join ",", @$methods) =~ s/^dsn=//i; $slaves = $self->get_cxn_from_dsn_table( %args, dsn_table_dsn => $dsn_table_dsn, ); } - elsif ( $method =~ m/none/i ) { - # https://bugs.launchpad.net/percona-toolkit/+bug/987694 + elsif ( $methods->[0] =~ m/none/i ) { PTDEBUG && _d('Not getting to slaves'); } else { - die "Invalid --recursion-method: $method. Valid values are: " - . "dsn=DSN, hosts, or processlist.\n"; + die "Unexpected recursion methods: @$methods"; } - + return $slaves; } +sub _resolve_recursion_methods { + my ($self, $dsn) = @_; + my $o = $self->{OptionParser}; + if ( $o->got('recursion-method') ) { + # Use whatever the user explicitly gave on the command line. + return $o->get('recursion-method'); + } + elsif ( $dsn && ($dsn->{P} || 3306) != 3306 ) { + # Special case: hosts is best when port is non-standard. + PTDEBUG && _d('Port number is non-standard; using only hosts method'); + return [qw(hosts)]; + } + else { + # Use the option's default. + return $o->get('recursion-method'); + } +} + # Sub: recurse_to_slaves # Descend to slaves by examining SHOW SLAVE HOSTS. # The callback gets the slave's DSN, dbh, parent, and the recursion level @@ -111,10 +151,16 @@ sub get_slaves { sub recurse_to_slaves { my ( $self, $args, $level ) = @_; $level ||= 0; - my $dp = $args->{dsn_parser}; - my $dsn = $args->{dsn}; + my $dp = $self->{DSNParser}; + my $recurse = $args->{recurse} || $self->{OptionParser}->get('recurse'); + my $dsn = $args->{dsn}; - if ( lc($args->{method} || '') eq 'none' ) { + # Re-resolve the recursion methods for each slave. In most cases + # it won't change, but it could if one slave uses standard port (3306) + # and another does not. + my $methods = $self->_resolve_recursion_methods($dsn); + PTDEBUG && _d('Recursion methods:', @$methods); + if ( lc($methods->[0]) eq 'none' ) { # https://bugs.launchpad.net/percona-toolkit/+bug/987694 PTDEBUG && _d('Not recursing to slaves'); return; @@ -154,13 +200,13 @@ sub recurse_to_slaves { # Call the callback! $args->{callback}->($dsn, $dbh, $level, $args->{parent}); - if ( !defined $args->{recurse} || $level < $args->{recurse} ) { + if ( !defined $recurse || $level < $recurse ) { # Find the slave hosts. Eliminate hosts that aren't slaves of me (as # revealed by server_id and master_id). my @slaves = grep { !$_->{master_id} || $_->{master_id} == $id } # Only my slaves. - $self->find_slave_hosts($dp, $dbh, $dsn, $args->{method}); + $self->find_slave_hosts($dp, $dbh, $dsn, $methods); foreach my $slave ( @slaves ) { PTDEBUG && _d('Recursing from', @@ -184,27 +230,14 @@ sub recurse_to_slaves { # If a method is given, it becomes the preferred (first tried) method. # Searching stops as soon as a method finds slaves. sub find_slave_hosts { - my ( $self, $dsn_parser, $dbh, $dsn, $method ) = @_; + my ( $self, $dsn_parser, $dbh, $dsn, $methods ) = @_; - my @methods = qw(processlist hosts); - if ( $method ) { - # Remove all but the given method. - @methods = grep { $_ ne $method } @methods; - # Add given method to the head of the list. - unshift @methods, $method; - } - else { - if ( ($dsn->{P} || 3306) != 3306 ) { - PTDEBUG && _d('Port number is non-standard; using only hosts method'); - @methods = qw(hosts); - } - } PTDEBUG && _d('Looking for slaves on', $dsn_parser->as_string($dsn), - 'using methods', @methods); + 'using methods', @$methods); my @slaves; METHOD: - foreach my $method ( @methods ) { + foreach my $method ( @$methods ) { my $find_slaves = "_find_slaves_by_$method"; PTDEBUG && _d('Finding slaves with', $find_slaves); @slaves = $self->$find_slaves($dsn_parser, $dbh, $dsn); @@ -849,13 +882,16 @@ sub reset_known_replication_threads { sub get_cxn_from_dsn_table { my ($self, %args) = @_; - my @required_args = qw(dsn_table_dsn make_cxn DSNParser Quoter); + my @required_args = qw(dsn_table_dsn make_cxn); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } - my ($dsn_table_dsn, $make_cxn, $dp, $q) = @args{@required_args}; + my ($dsn_table_dsn, $make_cxn) = @args{@required_args}; PTDEBUG && _d('DSN table DSN:', $dsn_table_dsn); + my $dp = $self->{DSNParser}; + my $q = $self->{Quoter}; + my $dsn = $dp->parse($dsn_table_dsn); my $dsn_table; if ( $dsn->{D} && $dsn->{t} ) { diff --git a/t/lib/MasterSlave.t b/t/lib/MasterSlave.t index 8207bd1f..2494e428 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 => 52; +use Test::More; use MasterSlave; use DSNParser; @@ -20,13 +20,13 @@ use Cxn; use Sandbox; use PerconaTest; -my $ms = new MasterSlave(); +use Data::Dumper; + 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'); - my $master_dsn = { h => '127.1', P => '12345', @@ -34,25 +34,29 @@ my $master_dsn = { p => 'msandbox', }; -# ############################################################################ -# get_slaves() wrapper around recurse_to_slaves() -# ############################################################################ my $q = new Quoter; my $o = new OptionParser(description => 'MasterSlave'); $o->get_specs("$trunk/bin/pt-table-checksum"); +my $ms = new MasterSlave( + OptionParser => $o, + DSNParser => $dp, + Quoter => $q, +); + +# ############################################################################ +# get_slaves() wrapper around recurse_to_slaves() +# ############################################################################ + SKIP: { skip "Cannot connect to sandbox master", 2 unless $master_dbh; - @ARGV = (); + local @ARGV = (); $o->get_opts(); - + my $slaves = $ms->get_slaves( - dbh => $master_dbh, - dsn => $master_dsn, - OptionParser => $o, - DSNParser => $dp, - Quoter => $q, - make_cxn => sub { + dbh => $master_dbh, + dsn => $master_dsn, + make_cxn => sub { my $cxn = new Cxn( @_, DSNParser => $dp, @@ -78,7 +82,7 @@ SKIP: { master_id => 12345, source => 'hosts', }, - 'get_slaves() from recurse_to_slaves()' + 'get_slaves() from recurse_to_slaves() with a default --recursion-method' ); my ($id) = $slaves->[0]->dbh()->selectrow_array('SELECT @@SERVER_ID'); @@ -93,16 +97,13 @@ SKIP: { # and ignore it. This tests nonetheless that "processlist" isn't # misspelled, which would cause the sub to die. # https://bugs.launchpad.net/percona-toolkit/+bug/921802 - @ARGV = ('--recursion-method', 'processlist'); + local @ARGV = ('--recursion-method', 'processlist'); $o->get_opts(); $slaves = $ms->get_slaves( - OptionParser => $o, - DSNParser => $dp, - Quoter => $q, - dbh => $master_dbh, - dsn => $master_dsn, - make_cxn => sub { + dbh => $master_dbh, + dsn => $master_dsn, + make_cxn => sub { my $cxn = new Cxn( @_, DSNParser => $dp, @@ -146,12 +147,9 @@ SKIP: { throws_ok( sub { $slaves = $ms->get_slaves( - OptionParser => $o, - DSNParser => $dp, - Quoter => $q, - dbh => $ro_dbh, - dsn => $ro_dsn, - make_cxn => sub { + dbh => $ro_dbh, + dsn => $ro_dsn, + make_cxn => sub { my $cxn = new Cxn( @_, DSNParser => $dp, @@ -169,12 +167,9 @@ SKIP: { @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 { + dbh => $ro_dbh, + dsn => $ro_dsn, + make_cxn => sub { my $cxn = new Cxn( @_, DSNParser => $dp, @@ -190,14 +185,13 @@ SKIP: { "No privs needed for --recursion-method=none (bug 987694)" ); + @ARGV = ('--recursion-method', 'none', '--recurse', '2'); + $o->get_opts(); my $recursed = 0; $ms->recurse_to_slaves( - { dsn_parser => $dp, - dbh => $ro_dbh, - dsn => $ro_dsn, - recurse => 2, - callback => sub { $recursed++ }, - method => 'none', + { dbh => $ro_dbh, + dsn => $ro_dsn, + callback => sub { $recursed++ }, }); is( $recursed, @@ -231,10 +225,10 @@ foreach my $port ( values %port_for ) { diag(`$trunk/sandbox/stop-sandbox $port >/dev/null 2>&1`); } } -diag(`$trunk/sandbox/start-sandbox master 2900 >/dev/null 2>&1`); -diag(`$trunk/sandbox/start-sandbox slave 2903 2900 >/dev/null 2>&1`); -diag(`$trunk/sandbox/start-sandbox slave 2901 2900 >/dev/null 2>&1`); -diag(`$trunk/sandbox/start-sandbox slave 2902 2901 >/dev/null 2>&1`); +diag(`$trunk/sandbox/start-sandbox master 2900`); +diag(`$trunk/sandbox/start-sandbox slave 2903 2900`); +diag(`$trunk/sandbox/start-sandbox slave 2901 2900`); +diag(`$trunk/sandbox/start-sandbox slave 2902 2901`); # I discovered something weird while updating this test. Above, you see that # slave2 is started first, then the others. Before, slave2 was started last, @@ -279,11 +273,12 @@ my $skip_callback = sub { . " from $dsn->{source}"); }; +@ARGV = ('--recurse', '2'); +$o->get_opts(); + $ms->recurse_to_slaves( - { dsn_parser => $dp, - dbh => $dbh, + { dbh => $dbh, dsn => $dsn, - recurse => 2, callback => $callback, skip_callback => $skip_callback, }); @@ -707,13 +702,40 @@ is( 'dbh created from DSN table works' ); +# ############################################################################ +# Invalid recursion methods are caught +# ############################################################################ +local $EVAL_ERROR; +eval { + MasterSlave::check_recursion_method([qw(stuff)]) +}; + +like( + $EVAL_ERROR, + qr/Invalid recursion method: stuff/, + "--recursion-method stuff causes error" +); + +local $EVAL_ERROR; +eval { + MasterSlave::check_recursion_method([qw(processlist stuff)]) +}; + +like( + $EVAL_ERROR, + qr/Invalid combination of recursion methods: processlist, stuff/, + "--recursion-method processlist,stuff causes error", +); + # ############################################################################# # Done. # ############################################################################# $sb->wipe_clean($master_dbh); -diag(`$trunk/sandbox/stop-sandbox 2903 2902 2901 2900 >/dev/null 2>&1`); +diag(`$trunk/sandbox/stop-sandbox 2903 2902 2901 2900`); diag(`/tmp/12346/use -e "set global read_only=1"`); diag(`/tmp/12347/use -e "set global read_only=1"`); -diag(`$trunk/sandbox/test-env reset >/dev/null 2>&1`); +$sb->wait_for_slaves(); +diag(`$trunk/sandbox/test-env reset`); ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); +done_testing; exit; diff --git a/t/pt-table-checksum/option_sanity.t b/t/pt-table-checksum/option_sanity.t index 56b4ce23..0e17cbbd 100644 --- a/t/pt-table-checksum/option_sanity.t +++ b/t/pt-table-checksum/option_sanity.t @@ -9,7 +9,7 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 19; +use Test::More; use PerconaTest; shift @INC; # our unshift (above) @@ -95,6 +95,18 @@ like( "Default --replicate-check=TRUE" ); +like( + $output, + qr/^\s+--recursion-method=a/m, + "--recursion-method is an array" +); + +like( + $output, + qr/^\s+--recursion-method\s+processlist,hosts/m, + "Default --recursion-method is processlist,hosts" +); + # ############################################################################ # Check opts that disable other opts. # ############################################################################ @@ -170,4 +182,4 @@ like( # ############################################################################# # Done. # ############################################################################# -exit; +done_testing;