#!/usr/bin/env perl BEGIN { die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; }; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; use JSON; use File::Temp qw(tempdir); use Percona::Test; use Percona::Test::Mock::UserAgent; require "$trunk/bin/pt-agent"; Percona::Toolkit->import(qw(Dumper)); Percona::WebAPI::Representation->import(qw(as_hashref)); # Running the agent is going to cause it to schedule the services, # i.e. write a real crontab. The test box/user shouldn't have a # crontab, so we'll warn and clobber it if there is one. my $crontab = `crontab -l 2>/dev/null`; if ( $crontab ) { warn "Removing crontab: $crontab\n"; `crontab -r`; } # ############################################################################# # Create mock client and Agent # ############################################################################# # These aren't the real tests yet: to run_agent, first we need # a client and Agent, so create mock ones. my $output; my $json = JSON->new->canonical([1])->pretty; $json->allow_blessed([]); $json->convert_blessed([]); my $ua = Percona::Test::Mock::UserAgent->new( encode => sub { my $c = shift; return $json->encode($c || {}) }, ); my $client = eval { Percona::WebAPI::Client->new( api_key => '123', ua => $ua, ); }; is( $EVAL_ERROR, '', 'Create mock client' ) or die; my $agent = Percona::WebAPI::Resource::Agent->new( id => '123', hostname => 'host', links => { self => '/agents/123', config => '/agents/123/config', }, ); my @wait; my $interval = sub { my $t = shift; push @wait, $t; print "interval=" . (defined $t ? $t : 'undef') . "\n"; }; # ############################################################################# # Test run_agent # ############################################################################# # The agent does just basically 2 things: check for new config, and # check for new services. It doesn't do the latter until it has a # config, because services require info from a config. Config are # written to $HOME/.pt-agent.conf; this can't be changed because the # other processes (service runner and spool checker) must share the # same config. my $config = Percona::WebAPI::Resource::Config->new( id => '1', name => 'Default', options => { 'check-interval' => "60", }, links => { self => '/agents/123/config', services => '/agents/123/services', }, ); my $run0 = Percona::WebAPI::Resource::Run->new( number => '0', program => 'pt-query-digest', options => '--output json', output => 'spool', ); my $svc0 = Percona::WebAPI::Resource::Service->new( name => 'query-monitor', run_schedule => '1 * * * *', spool_schedule => '2 * * * *', runs => [ $run0 ], links => { send_data => '/query-monitor', }, ); $ua->{responses}->{get} = [ { headers => { 'X-Percona-Resource-Type' => 'Config' }, content => as_hashref($config, with_links => 1), }, { headers => { 'X-Percona-Resource-Type' => 'Service' }, content => [ as_hashref($svc0, with_links => 1) ], }, ]; # The only thing pt-agent must have is the API key in the config file, # everything else relies on defaults until the first Config is gotten # from Percona. -- The tool calls init_config_file() if the file doesn't # exist, so we do the same. Might as well test it while we're here. my $config_file = pt_agent::get_config_file(); unlink $config_file if -f $config_file; pt_agent::init_config_file(file => $config_file, api_key => '123'); is( `cat $config_file`, "api-key=123\n", "init_config_file()" ); my $tmpdir = tempdir("/tmp/pt-agent.$PID.XXXXXX", CLEANUP => 1); mkdir "$tmpdir/services" or die "Error making $tmpdir/services: $OS_ERROR"; my @ok_code = (); # callbacks my @oktorun = (1, 0); my $oktorun = sub { my $ok = shift @oktorun; print "oktorun=" . (defined $ok ? $ok : 'undef') . "\n"; my $code = shift @ok_code; $code->() if $code; return $ok }; @wait = (); $output = output( sub { pt_agent::run_agent( agent => $agent, client => $client, interval => $interval, config_file => $config_file, lib_dir => $tmpdir, oktorun => $oktorun, # optional, for testing json => $json, # optional, for testing ); }, stderr => 1, ); is( `cat $config_file`, "api-key=123\ncheck-interval=60\n", "Write Config to config file" ); is( scalar @wait, 1, "Called interval once" ); is( $wait[0], 60, "... used Config->options->check-interval" ); ok( -f "$tmpdir/services/query-monitor", "Created services/query-monitor" ) or diag($output); chomp(my $n_files = `ls -1 $tmpdir/services| wc -l | awk '{print \$1}'`); is( $n_files, 1, "... only created services/query-monitor" ); ok( no_diff( "cat $tmpdir/services/query-monitor", "t/pt-agent/samples/service001", ), "query-monitor service file" ); $crontab = `crontab -l 2>/dev/null`; like( $crontab, qr/pt-agent --run-service query-monitor$/m, "Scheduled --run-service with crontab" ); like( $crontab, qr/pt-agent --send-data query-monitor$/m, "Scheduled --send-data with crontab" ); # ############################################################################# # Run run_agent again, like the agent had been stopped and restarted. # ############################################################################# $ua->{responses}->{get} = [ # First check, fail { code => 500, }, # interval # 2nd check, init with latest Config and Services { headers => { 'X-Percona-Resource-Type' => 'Config' }, content => as_hashref($config), }, { headers => { 'X-Percona-Resource-Type' => 'Service' }, content => [ as_hashref($svc0) ], }, # interval # 3rd check, same Config and Services so nothing to do { headers => { 'X-Percona-Resource-Type' => 'Config' }, content => as_hashref($config), }, { headers => { 'X-Percona-Resource-Type' => 'Service' }, content => [ as_hashref($svc0) ], }, # interval, oktorun=0 ]; # 0=while check, 1=after first check, 2=after 2nd check, etc. @oktorun = (1, 1, 1, 0); # Between the 2nd and 3rd checks, remove the config file (~/.pt-agent.conf) # and query-monitor service file. When the tool re-GETs these, they'll be # the same so it won't recreate them. A bug here will cause these files to # exist again after running. $ok_code[2] = sub { unlink "$config_file"; unlink "$tmpdir/services/query-monitor"; Percona::Test::wait_until(sub { ! -f "$config_file" }); Percona::Test::wait_until(sub { ! -f "$tmpdir/services/query-monitor" }); }; @wait = (); $output = output( sub { pt_agent::run_agent( agent => $agent, client => $client, interval => $interval, config_file => $config_file, lib_dir => $tmpdir, oktorun => $oktorun, # optional, for testing json => $json, # optional, for testing ); }, stderr => 1, ); is_deeply( \@wait, [ undef, 60, 60 ], "Got Config after error" ) or diag(Dumper(\@wait)); ok( ! -f "$config_file", "No Config diff, no config file change" ); ok( ! -f "$tmpdir/services/query-monitor", "No Service diff, no service file changes" ); my $new_crontab = `crontab -l 2>/dev/null`; is( $new_crontab, $crontab, "Crontab is the same" ); # ############################################################################# # Done. # ############################################################################# # This shouldn't cause an error, but if it does, let it show up # in the results as an error. `crontab -r`; if ( -f $config_file ) { unlink $config_file or warn "Error removing $config_file: $OS_ERROR"; } done_testing;