1#!/usr/bin/env perl
2
3BEGIN {
4   die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
5      unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
6   unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
7};
8
9use strict;
10use warnings FATAL => 'all';
11use English qw(-no_match_vars);
12use Time::HiRes qw(sleep);
13use Test::More;
14
15use PerconaTest;
16use Sandbox;
17require "$trunk/bin/pt-kill";
18
19use Data::Dumper;
20$Data::Dumper::Indent    = 1;
21$Data::Dumper::Sortkeys  = 1;
22$Data::Dumper::Quotekeys = 0;
23
24my $dp  = new DSNParser(opts=>$dsn_opts);
25my $sb  = new Sandbox(basedir => '/tmp', DSNParser => $dp);
26my $master_dbh = $sb->get_dbh_for('master');
27my $slave_dbh  = $sb->get_dbh_for('slave1');
28my $target_dbh = $sb->get_dbh_for('master');
29
30if ( !$master_dbh ) {
31   plan skip_all => 'Cannot connect to sandbox master';
32}
33elsif ( !$target_dbh ) {
34   plan skip_all => 'Cannot connect to sandbox master (target)';
35}
36elsif ( !$slave_dbh ) {
37   plan skip_all => 'Cannot connect to sandbox slave';
38}
39
40my $output;
41my $master_dsn = $sb->dsn_for('master');
42my $master_cnf = $sb->cnf_for('master');
43my $slave_dsn  = $sb->dsn_for('slave1');
44
45# Create the --log-dsn table.
46$sb->create_dbs($master_dbh, [qw(kill_test)]);
47my $log_table = "kill_test.log_table";
48my $log_dsn   = "D=kill_test,t=log_table";
49my $log_sql   = OptionParser->read_para_after(
50   "$trunk/bin/pt-kill", qr/MAGIC_create_log_table/);
51$log_sql =~ s/kill_log/$log_table/;
52$master_dbh->do($log_sql);
53$sb->wait_for_slaves();
54
55# Create the target db for --match-db.
56my $target_db = "x$PID";
57$sb->create_dbs($target_dbh, [$target_db]);
58
59sub setup_target {
60   eval {
61      $target_dbh->do("SELECT 1");
62   };
63   if ( $EVAL_ERROR ) {
64      eval {
65         $target_dbh->disconnect();
66      };
67      $target_dbh = $sb->get_dbh_for('master');
68   }
69   $target_dbh->do("USE $target_db");
70}
71
72# #############################################################################
73# Require D and t in --log-dsn
74# #############################################################################
75
76foreach my $test (
77   [q/h=127.1,P=12345,u=msandbox,p=msandbox/, 'D and t'],
78   [q/h=127.1,P=12345,u=msandbox,p=msandbox,t=log_table/, 'D'],
79   [q/h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test/, 't'],
80) {
81   eval {
82      pt_kill::main($master_dsn, qw(--kill --run-time 1 --interval 1),
83         "--match-db", $target_db,
84         "--log-dsn", $test->[0],
85      )
86   };
87   like(
88      $EVAL_ERROR,
89      qr/\Q--log-dsn does not specify a database (D) or a database-qualified table (t)\E/,
90      "--log-dsn croaks if missing $test->[1]"
91   );
92}
93
94# #############################################################################
95# Basic usage
96# #############################################################################
97
98eval {
99   setup_target();
100   pt_kill::main($master_dsn, qw(--kill --run-time 1 --interval 1),
101      "--match-db", $target_db,
102      "--log-dsn", "$master_dsn,$log_dsn"
103   )
104};
105
106is(
107   $EVAL_ERROR,
108   '',
109   "--log-dsn with existing log table, no error"
110);
111
112# Should get a row like:
113# $VAR1 = [
114#   {
115#      command => 'Sleep',
116#      db => 'x32282',
117#      host => 'localhost:62581',
118#      id => '365',
119#      info => undef,
120#      kill_error => '',
121#      kill_id => '1',
122#      reason => 'Query matches db spec',
123#      server_id => '12345',
124#      state => '',
125#      time => '0',
126#      time_ms => undef,
127#      timestamp => '2013-08-12 12:46:26',
128#      user => 'msandbox'
129#   }
130# ];
131my $rows = $master_dbh->selectall_arrayref(
132   "SELECT * FROM $log_table", { Slice =>{} });
133
134is(
135   scalar @$rows,
136   1,
137   "... which contains one row"
138) or diag(Dumper($rows));
139
140is(
141   $rows->[0]->{db},
142   $target_db,
143   "... got the target db"
144) or diag(Dumper($rows));
145
146is(
147   $rows->[0]->{server_id},
148   12345,
149   "... on the correct server"
150) or diag(Dumper($rows));
151
152is(
153   $rows->[0]->{reason},
154   'Query matches db spec',
155   "... correct kill reason"
156) or diag(Dumper($rows));
157
158# Get the current ts in MySQL's format.
159my $current_ts = Transformers::ts(time());
160($current_ts) = $master_dbh->selectrow_array("SELECT TIMESTAMP('$current_ts')");
161
162# Chop off the minutes & seconds. If the rest of the date is right,
163# this is unlikely to be broken.
164substr($current_ts, -5, 5, "");
165like(
166   $rows->[0]->{timestamp},
167   qr/\A\Q$current_ts\E.{5}\Z/,
168   "... timestamp is correct (bug 1086259)"
169);
170
171my $against = {
172   user    => 'msandbox',
173   host    => 'localhost',
174   db      => $target_db,
175   command => 'Sleep',
176   state   => '', #($sandbox_version lt '5.1' ? "executing" : "User sleep"),
177   info    => undef,
178};
179my %trimmed_result;
180@trimmed_result{ keys %$against } = @{$rows->[0]}{ keys %$against };
181$trimmed_result{host} =~ s/localhost:[0-9]+/localhost/;
182
183is_deeply(
184   \%trimmed_result,
185   $against,
186   "... populated as expected",
187) or diag(Dumper($rows));
188
189# #############################################################################
190# --create-log-table
191# #############################################################################
192
193# XXX This test assumes that the log table exists from previous tests.
194
195eval {
196   setup_target();
197   pt_kill::main('-F', $master_cnf, qw(--kill --run-time 1 --interval 1),
198      "--create-log-table",
199      "--match-info", 'select sleep\(4\)',
200      "--log-dsn", "$master_dsn,$log_dsn",
201   )
202};
203
204is(
205   $EVAL_ERROR,
206   '',
207   "--log-dsn --create-log-table and the table exists, no error"
208);
209
210$master_dbh->do("DROP TABLE IF EXISTS $log_table");
211$sb->wait_for_slaves();
212
213eval {
214   setup_target();
215   pt_kill::main('-F', $master_cnf, qw(--kill --run-time 1 --interval 1),
216      "--create-log-table",
217      "--match-info", 'select sleep\(4\)',
218      "--log-dsn", "$master_dsn,$log_dsn",
219   )
220};
221
222is(
223   $EVAL_ERROR,
224   '',
225   "--log-dsn --create-log-table and the table doesn't exist, no error"
226);
227
228$master_dbh->do("DROP TABLE IF EXISTS $log_table");
229$sb->wait_for_slaves();
230
231eval {
232   setup_target();
233   pt_kill::main('-F', $master_cnf, qw(--kill --run-time 1 --interval 1),
234      "--match-info", 'select sleep\(4\)',
235      "--log-dsn", "$master_dsn,$log_dsn",
236   )
237};
238
239like(
240   $EVAL_ERROR,
241   qr/\Q--log-dsn table does not exist. Please create it or specify\E/,
242   "--create-log-table is off by default"
243);
244
245# Re-create the log table for the next tests.
246$master_dbh->do($log_sql);
247$sb->wait_for_slaves();
248
249# #############################################################################
250# Can re-use the log table.
251# #############################################################################
252
253for (1,2) {
254   setup_target();
255   pt_kill::main($master_dsn, qw(--kill --run-time 1 --interval 1),
256      "--create-log-table",
257      "--match-db", $target_db,
258      "--log-dsn", "$master_dsn,$log_dsn",
259   );
260   sleep 0.5;
261}
262
263$rows = $master_dbh->selectall_arrayref("SELECT * FROM $log_table");
264
265is(
266   scalar @$rows,
267   2,
268   "Different --log-dsn runs reuse the log table"
269) or diag(Dumper($rows));
270
271# #############################################################################
272# --log-dsn and --daemonize
273# https://bugs.launchpad.net/percona-toolkit/+bug/1209436
274# #############################################################################
275
276$master_dbh->do("TRUNCATE $log_table");
277$sb->wait_for_slaves();
278
279my $pid_file = "/tmp/pt-kill-test.$PID";
280my $log_file = "/tmp/pt-kill-test-log.$PID";
281diag(`rm -f $pid_file $log_file >/dev/null 2>&1`);
282
283setup_target();
284system("$trunk/bin/pt-kill $master_dsn --daemonize --run-time 1 --kill-query --interval 1 --match-db $target_db --log-dsn $slave_dsn,$log_dsn --pid $pid_file --log $log_file");
285PerconaTest::wait_for_files($pid_file);         # start
286# ...                                           # run
287PerconaTest::wait_until(sub { !-f $pid_file});  # stop
288
289# Should *not* log to master
290$rows = $master_dbh->selectall_arrayref("SELECT * FROM $log_table");
291ok(
292   !@$rows,
293   "--log-dsn --daemonize, master (bug 1209436)",
294) or diag(Dumper($rows));
295
296# Should log to slave
297$rows = $slave_dbh->selectall_arrayref("SELECT * FROM $log_table");
298ok(
299   scalar @$rows,
300   "--log-dsn --daemonize, slave (bug 1209436)"
301) or diag(Dumper($rows));
302
303# #############################################################################
304# --log-dsn in a --config file
305# https://bugs.launchpad.net/percona-toolkit/+bug/1209436
306# #############################################################################
307
308$master_dbh->do("TRUNCATE $log_table");
309$sb->wait_for_slaves();
310
311my $cnf_file = "/tmp/pt-kill-test.cnf.$PID";
312diag(`rm -f $pid_file $log_file $cnf_file >/dev/null 2>&1`);
313
314open my $fh, '>', $cnf_file or die "Error opening $cnf_file: $OS_ERROR";
315print { $fh } <<EOF;
316defaults-file=$master_cnf
317log-dsn=$slave_dsn,$log_dsn
318daemonize
319run-time=1
320kill-query
321interval=1
322match-db=$target_db
323pid=$pid_file
324log=$log_file
325EOF
326close $fh;
327
328setup_target();
329system("$trunk/bin/pt-kill --config $cnf_file");
330PerconaTest::wait_for_files($pid_file);         # start
331# ...                                           # run
332PerconaTest::wait_until(sub { !-f $pid_file});  # stop
333
334# Should *not* log to master
335$rows = $master_dbh->selectall_arrayref("SELECT * FROM $log_table");
336ok(
337   !@$rows,
338   "--log-dsn in --config file, master (bug 1209436)",
339) or diag(Dumper($rows));
340
341# Should log to slave
342$rows = $slave_dbh->selectall_arrayref("SELECT * FROM $log_table");
343ok(
344   scalar @$rows,
345   "--log-dsn in --config file, slave (bug 1209436)"
346) or diag(Dumper($rows));
347
348diag(`rm -f $pid_file $log_file $cnf_file >/dev/null 2>&1`);
349
350# #############################################################################
351# Done.
352# #############################################################################
353eval { $target_dbh->disconnect() };
354$sb->wipe_clean($master_dbh);
355$sb->wipe_clean($slave_dbh);
356ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
357done_testing;
358