Fix and test error on startup if config file is missing. Refactor run_agent(): move Agent setup stuff into start_agent(). Move related subs next to each other.

This commit is contained in:
Daniel Nichter
2013-04-13 11:27:45 -06:00
parent 2d89fe6163
commit a32a583e05
3 changed files with 575 additions and 501 deletions
+456 -427
View File
@@ -4908,7 +4908,18 @@ sub main {
# but don't create.
my $config_file = get_config_file();
_err("$config_file exists but is not writable")
unless -f $config_file && -w $config_file;
if -f $config_file && !-w $config_file;
# Run the agent's main loop which doesn't return until the service
# is stopped, killed, or has an internal bug.
my $running = start_agent(
api_key => $api_key,
Cxn => $cxn,
lib_dir => $o->get('lib'),
daemonize => $o->get('daemonize'),
pid_file => $o->get('pid'),
log_file => $o->get('log'),
);
# Wait time between checking for new config and services.
# Use the tool's built-in default until a config is gotten,
@@ -4922,16 +4933,13 @@ sub main {
sleep $t;
};
# Run the agent's main loop which doesn't return until the service
# is stopped, killed, or has an internal bug.
run_agent(
api_key => $api_key,
agent => $running->{agent},
client => $running->{client},
daemon => $running->{daemon},
interval => $check_wait,
Cxn => $cxn,
lib_dir => $o->get('lib'),
daemonize => $o->get('daemonize'),
pid_file => $o->get('pid'),
log_file => $o->get('log'),
);
_info("pt-agent exit $exit_status, oktorun $oktorun");
@@ -5099,413 +5107,6 @@ sub init_agent {
return $agent;
}
# ################################ #
# Agent (main daemon) process subs #
# ################################ #
# Run the agent, i.e. exec the main loop to check/update the config
# and services. Doesn't return until the service is stopped or killed.
sub run_agent {
my (%args) = @_;
have_required_args(\%args, qw(
api_key
interval
lib_dir
Cxn
)) or die;
my $api_key = $args{api_key};
my $interval = $args{interval};
my $lib_dir = $args{lib_dir};
my $cxn = $args{Cxn};
# Optional args
my $agent_uuid = $args{agent_uuid};
my $daemonize = $args{daemonize};
my $pid_file = $args{pid_file};
my $log_file = $args{log_file};
my $_oktorun = $args{oktorun} || sub { return $oktorun };
my $versions = $args{versions}; # for testing
my $client = $args{client}; # for testing
my $entry_links = $args{entry_links}; # for testing
my $agent = $args{agent}; # for testing
# Daemonize first so all output goes to the --log.
my %daemon_args = (
daemonize => $daemonize,
pid_file => $pid_file,
log_file => $log_file,
);
my $daemon = Daemon->new(
%daemon_args,
parent_exit => sub {
my $child_pid = shift;
print "pt-agent has daemonized and is running as PID $child_pid:
--lib " . ($lib_dir || '') . "
--log " . ($log_file || '') . "
--pid " . ($pid_file || '') . "
These values can change if a different configuration is received.
Configure the agent at https://pws.percona.com/agents
",
}
);
$daemon->run();
# If we daemonized, the parent has already exited and we're the child.
# We shared a copy of every Cxn with the parent, and the parent's copies
# were destroyed but the dbhs were not disconnected because the parent
# attrib was true. Now, as the child, set it false so the dbhs will be
# disconnected when our Cxn copies are destroyed. If we didn't daemonize,
# then we're not really a parent (since we have no children), so set it
# false to auto-disconnect the dbhs when our Cxns are destroyed.
$cxn->{parent} = 0;
eval {
init_lib_dir(
lib_dir => $lib_dir,
);
};
if ( $EVAL_ERROR ) {
chomp($EVAL_ERROR);
_info("Error initializing --lib $lib_dir: $EVAL_ERROR. "
. "Configure the agent at https://pws.percona.com "
. "to use a writeable --lib directory.");
}
# Connect to https://api.pws.percona.com and get entry links.
# Don't return until successful.
if ( !$client || !$entry_links ) {
($client, $entry_links) = get_api_client(
api_key => $api_key,
tries => undef, # forever
interval => sub { sleep 60 },
);
}
return unless $_oktorun->();
# Do a version-check every time the agent starts. If versions
# have changed, this can affect how services are implemented.
# Since this is the only thing we use the Cxn for, get_versions()
# connects and disconnect it, if possible. If not possible, the
# MySQL version isn't sent in hopes that it becomes possible to get
# it later.
if ( !$versions ) {
$versions = get_versions(
Cxn => $cxn,
tries => 1,
);
}
return unless $_oktorun->();
# Load and update the local (i.e. existing) agent, or create a new one.
my ($action, $link);
if ( !$agent ) {
if ( $agent_uuid ) {
_info("Re-creating Agent with UUID $agent_uuid");
$agent = Percona::WebAPI::Resource::Agent->new(
uuid => $agent_uuid,
versions => $versions,
);
$action = 'put'; # update
$link = $entry_links->{agents} . '/' . $agent->uuid;
}
else {
# First try to load the local agent.
$agent = load_local_agent(
lib_dir => $lib_dir,
);
if ( $agent ) {
# Loaded local agent.
$action = 'put'; # update
$link = $entry_links->{agents} . '/' . $agent->uuid;
$agent->{versions} = $versions;
}
else {
# No local agent and --agent-uuid wasn't give.
$agent = Percona::WebAPI::Resource::Agent->new(
versions => $versions,
);
$action = 'post'; # create
$link = $entry_links->{agents};
}
}
}
else {
$action = 'put';
$link = $entry_links->{agents} . '/' . $agent->uuid;
}
$agent = init_agent(
agent => $agent,
action => $action,
link => $link,
client => $client,
interval => sub { sleep 60 },
lib_dir => $lib_dir,
oktorun => $_oktorun,
);
save_agent(
agent => $agent,
lib_dir => $lib_dir,
);
return unless $_oktorun->();
_info('Running agent ' . $agent->name);
# #######################################################################
# Main agent loop
# #######################################################################
$state->{first_config} = 1;
my $first_config_interval = 60;
_info("Checking silently every $first_config_interval seconds"
. " for the first config");
my $success;
my $new_daemon;
my $config;
my $services;
while ( $_oktorun->() ) {
($config, $lib_dir, $new_daemon, $success) = get_config(
agent => $agent,
client => $client,
lib_dir => $lib_dir,
config => $config,
services => $services,
quiet => $state->{first_config},
daemon_args => \%daemon_args,
);
# Get services only if we successfully got the config because the services
# may depened on the current config, specifically the --spool dir.
if ( $success && $config && $config->links->{services} ) {
if ( $state->{first_config} ) {
delete $state->{first_config};
_info('Agent has been successfully configured');
}
if ( $new_daemon ) {
# NOTE: Daemon objects use DESTROY to auto-remove their pid file
# when they lose scope (i.e. ref count goes to zero). This
# assignment destroys (old) $daemon, so it auto-removes the old
# pid file. $new_daemon maintains scope and the new pid file
# by becoming $daemon which was defined in the outer scope so
# it won't destroy again when we leave this block. Fancy!
# About sharing_pid_file: see the comment in apply_config().
if ( $daemon_args{sharing_pid_file} ) {
$daemon->{pid_file_owner} = 0;
delete $daemon_args{sharing_pid_file};
}
$daemon = $new_daemon;
}
($services, $success) = get_services(
agent => $agent,
client => $client,
lib_dir => $lib_dir,
config => $config,
services => $services,
json => $args{json}, # optional, for testing
bin_dir => $args{bin_dir}, # optional, for testing
);
}
# If configured, wait the given interval. Else, retry more
# quickly so we're ready to go soon after we're configured.
$interval->(
$config ? ($config->options->{'check-interval'}, 0)
: ($first_config_interval , 1) # 1=quiet
);
}
# This shouldn't happen until the service is stopped/killed.
_info('Agent ' . $agent->name . ' has stopped');
return;
}
sub get_config {
my (%args) = @_;
have_required_args(\%args, qw(
agent
client
lib_dir
daemon_args
)) or die;
my $agent = $args{agent};
my $client = $args{client};
my $lib_dir = $args{lib_dir};
my $daemon_args = $args{daemon_args};
# Optional args
my $config = $args{config}; # may not be defined yet
my $services = $args{services}; # may not be defined yet
my $quiet = $args{quiet};
my $success = 0;
my $new_daemon;
_info('Getting config') unless $quiet;
my $new_config = eval {
$client->get(
link => $agent->links->{config},
);
};
if ( my $e = $EVAL_ERROR ) {
if (blessed($e)) {
if ($e->isa('Percona::WebAPI::Exception::Request')) {
if ( $e->status == 404 ) {
_info('Agent ' . $agent->name. ' is not configured.')
unless $quiet;
}
else {
_info("$e"); # PWS API error?
}
}
elsif ($e->isa('Percona::WebAPI::Exception::Resource')) {
_warn("$e");
}
}
else {
_err($e); # internal error
}
}
else {
eval {
if ( !$quiet ) {
_info("Running config: " . ($config ? $config->ts : ''));
_info("Current config: " . $new_config->ts);
}
if ( !$config || $new_config->ts > $config->ts ) {
($lib_dir, $new_daemon) = apply_config(
agent => $agent,
old_config => $config,
new_config => $new_config,
lib_dir => $lib_dir,
daemon_args => $daemon_args,
);
$config = $new_config;
$success = 1;
_info('Config ' . $config->ts . ' applied successfully');
}
else {
$success = 1;
_info('Config has not changed') unless $quiet;
}
};
if ( $EVAL_ERROR ) {
chomp $EVAL_ERROR;
_warn("Failed to apply config " . $new_config->ts
. ": $EVAL_ERROR Will try again.");
}
}
return ($config, $lib_dir, $new_daemon, $success);
}
sub get_services {
my (%args) = @_;
have_required_args(\%args, qw(
agent
client
lib_dir
)) or die;
my $agent = $args{agent};
my $client = $args{client};
my $lib_dir = $args{lib_dir};
# Optional args
my $config = $args{config}; # may not be defined yet
my $services = $args{services}; # may not be defined yet
my $success = 0;
eval {
_info('Getting services');
# Get services from Percona.
my $new_services = $client->get(
link => $config->links->{services},
);
# If the current and new services are different,
# write the new ones to disk, then schedule them.
if ( resource_diff($services, $new_services) ) {
_info('New services');
write_services(
services => $new_services,
lib_dir => $lib_dir,
json => $args{json}, # optional, for testing
);
# TODO: this probably can't/won't fail, but if it does, is it
# worth setting success=0?
start_services(
services => $new_services,
lib_dir => $lib_dir,
bin_dir => $args{bin_dir}, # optional, for testing
);
schedule_services(
services => $new_services,
lib_dir => $lib_dir,
bin_dir => $args{bin_dir}, # optional, for testing
);
$services = $new_services;
$success = 1;
_info('Services updated successfully: '
. join(', ', map { $_->name } @$services));
}
else {
_info('Services have not changed');
}
};
if ( $EVAL_ERROR ) {
_warn($EVAL_ERROR);
}
return ($services, $success);
}
# Write a Config resource to a Percona Toolkit config file,
# usually $HOME/pt-agent.conf.
sub write_config {
my (%args) = @_;
have_required_args(\%args, qw(
config
)) or die;
my $config = $args{config};
my $file = get_config_file();
_info("Writing config to $file");
# Get the api-key line if any; we don't want to/can't clobber this.
my $api_key;
if ( -f $file ) {
open my $fh, "<", $file
or die "Error opening $file: $OS_ERROR";
my $contents = do { local $/ = undef; <$fh> };
close $fh;
($api_key) = $contents =~ m/^(api-key=\S+)$/m;
}
# Re-write the api-key, if any, then write the config.
open my $fh, '>', $file
or die "Error opening $file: $OS_ERROR";
if ( $api_key ) {
print { $fh } $api_key, "\n"
or die "Error writing to $file: $OS_ERROR";
}
print { $fh } as_config($config)
or die "Error writing to $file: $OS_ERROR";
close $fh
or die "Error closing $file: $OS_ERROR";
return;
}
# Check and init the --lib dir. This dir is used to save the Agent resource
# (/agent), Service resources (/services/), and crontab for services(/conrtab,
# /crontab.err).
@@ -5553,6 +5154,330 @@ sub init_lib_dir {
return;
}
# ################################ #
# Agent (main daemon) process subs #
# ################################ #
sub start_agent {
my (%args) = @_;
have_required_args(\%args, qw(
api_key
lib_dir
Cxn
)) or die;
my $api_key = $args{api_key};
my $lib_dir = $args{lib_dir};
my $cxn = $args{Cxn};
# Optional args
my $agent_uuid = $args{agent_uuid};
my $daemonize = $args{daemonize};
my $pid_file = $args{pid_file};
my $log_file = $args{log_file};
my $_oktorun = $args{oktorun} || sub { return $oktorun };
my $versions = $args{versions}; # for testing
my $client = $args{client}; # for testing
my $entry_links = $args{entry_links}; # for testing
# Daemonize first so all output goes to the --log.
my $daemon = Daemon->new(
daemonize => $daemonize,
pid_file => $pid_file,
log_file => $log_file,
parent_exit => sub {
my $child_pid = shift;
print "pt-agent has daemonized and is running as PID $child_pid:
--lib " . ($lib_dir || '') . "
--log " . ($log_file || '') . "
--pid " . ($pid_file || '') . "
These values can change if a different configuration is received.
Configure the agent at https://pws.percona.com/agents
",
}
);
$daemon->run();
# If we daemonized, the parent has already exited and we're the child.
# We shared a copy of every Cxn with the parent, and the parent's copies
# were destroyed but the dbhs were not disconnected because the parent
# attrib was true. Now, as the child, set it false so the dbhs will be
# disconnected when our Cxn copies are destroyed. If we didn't daemonize,
# then we're not really a parent (since we have no children), so set it
# false to auto-disconnect the dbhs when our Cxns are destroyed.
$cxn->{parent} = 0;
_info('Starting agent');
eval {
init_lib_dir(
lib_dir => $lib_dir,
);
};
if ( $EVAL_ERROR ) {
chomp($EVAL_ERROR);
_info("Error initializing --lib $lib_dir: $EVAL_ERROR. "
. "Configure the agent at https://pws.percona.com "
. "to use a writeable --lib directory.");
}
# Connect to https://api.pws.percona.com and get entry links.
# Don't return until successful.
if ( !$client || !$entry_links ) {
($client, $entry_links) = get_api_client(
api_key => $api_key,
tries => undef, # forever
interval => sub { sleep 60 },
);
}
return unless $_oktorun->();
# Do a version-check every time the agent starts. If versions
# have changed, this can affect how services are implemented.
# Since this is the only thing we use the Cxn for, get_versions()
# connects and disconnect it, if possible. If not possible, the
# MySQL version isn't sent in hopes that it becomes possible to get
# it later.
if ( !$versions ) {
$versions = get_versions(
Cxn => $cxn,
tries => 1,
);
}
return unless $_oktorun->();
# Load and update the local (i.e. existing) agent, or create a new one.
my $agent;
my $action;
my $link;
if ( $agent_uuid ) {
_info("Re-creating Agent with UUID $agent_uuid");
$agent = Percona::WebAPI::Resource::Agent->new(
uuid => $agent_uuid,
versions => $versions,
);
$action = 'put'; # update
$link = $entry_links->{agents} . '/' . $agent->uuid;
}
else {
# First try to load the local agent.
$agent = load_local_agent(
lib_dir => $lib_dir,
);
if ( $agent ) {
# Loaded local agent.
$action = 'put'; # update
$link = $entry_links->{agents} . '/' . $agent->uuid;
$agent->{versions} = $versions;
}
else {
# No local agent and --agent-uuid wasn't give.
$agent = Percona::WebAPI::Resource::Agent->new(
versions => $versions,
);
$action = 'post'; # create
$link = $entry_links->{agents};
}
}
$agent = init_agent(
agent => $agent,
action => $action,
link => $link,
client => $client,
interval => sub { sleep 60 },
lib_dir => $lib_dir,
oktorun => $_oktorun,
);
save_agent(
agent => $agent,
lib_dir => $lib_dir,
);
return {
agent => $agent,
client => $client,
daemon => $daemon,
};
}
# Run the agent, i.e. exec the main loop to check/update the config
# and services. Doesn't return until the service is stopped or killed.
sub run_agent {
my (%args) = @_;
have_required_args(\%args, qw(
agent
client
daemon
interval
lib_dir
Cxn
)) or die;
my $agent = $args{agent};
my $client = $args{client};
my $daemon = $args{daemon};
my $interval = $args{interval};
my $lib_dir = $args{lib_dir};
my $cxn = $args{Cxn};
# Optional args
my $_oktorun = $args{oktorun} || sub { return $oktorun };
_info('Running agent ' . $agent->name);
# #######################################################################
# Main agent loop
# #######################################################################
$state->{first_config} = 1;
my $first_config_interval = 60;
_info("Checking silently every $first_config_interval seconds"
. " for the first config");
my $success;
my $new_daemon;
my $config;
my $services;
while ( $_oktorun->() ) {
($config, $lib_dir, $new_daemon, $success) = get_config(
link => $agent->links->{config},
agent => $agent,
client => $client,
daemon => $daemon,
lib_dir => $lib_dir,
config => $config,
quiet => $state->{first_config},
);
# Get services only if we successfully got the config because the services
# may depened on the current config, specifically the --spool dir.
if ( $success && $config && $config->links->{services} ) {
if ( $state->{first_config} ) {
delete $state->{first_config};
_info('Agent has been successfully configured');
}
if ( $new_daemon ) {
# NOTE: Daemon objects use DESTROY to auto-remove their pid file
# when they lose scope (i.e. ref count goes to zero). This
# assignment destroys (old) $daemon, so it auto-removes the old
# pid file. $new_daemon maintains scope and the new pid file
# by becoming $daemon which was defined in the outer scope so
# it won't destroy again when we leave this block. Fancy!
# About sharing_pid_file: see the comment in apply_config().
if ( $new_daemon->{sharing_pid_file} ) {
$daemon->{pid_file_owner} = 0;
delete $new_daemon->{sharing_pid_file};
}
$daemon = $new_daemon;
}
($services, $success) = get_services(
link => $config->links->{services},
agent => $agent,
client => $client,
lib_dir => $lib_dir,
services => $services,
json => $args{json}, # optional, for testing
bin_dir => $args{bin_dir}, # optional, for testing
);
}
# If configured, wait the given interval. Else, retry more
# quickly so we're ready to go soon after we're configured.
$interval->(
$config ? ($config->options->{'check-interval'}, 0)
: ($first_config_interval , 1) # 1=quiet
);
}
# This shouldn't happen until the service is stopped/killed.
_info('Agent ' . $agent->name . ' has stopped');
return;
}
sub get_config {
my (%args) = @_;
have_required_args(\%args, qw(
link
agent
client
daemon
lib_dir
)) or die;
my $link = $args{link};
my $agent = $args{agent};
my $client = $args{client};
my $daemon = $args{daemon};
my $lib_dir = $args{lib_dir};
# Optional args
my $config = $args{config}; # may not be defined yet
my $quiet = $args{quiet};
my $success = 0;
my $new_daemon;
_info('Getting config') unless $quiet;
my $new_config = eval {
$client->get(
link => $link,
);
};
if ( my $e = $EVAL_ERROR ) {
if (blessed($e)) {
if ($e->isa('Percona::WebAPI::Exception::Request')) {
if ( $e->status == 404 ) {
_info('Agent ' . $agent->name. ' is not configured.')
unless $quiet;
}
else {
_info("$e"); # PWS API error?
}
}
elsif ($e->isa('Percona::WebAPI::Exception::Resource')) {
_warn("$e");
}
}
else {
_err($e); # internal error
}
}
else {
eval {
if ( !$quiet ) {
_info("Running config: " . ($config ? $config->ts : ''));
_info("Current config: " . $new_config->ts);
}
if ( !$config || $new_config->ts > $config->ts ) {
($lib_dir, $new_daemon) = apply_config(
agent => $agent,
old_config => $config,
new_config => $new_config,
lib_dir => $lib_dir,
daemon => $daemon,
);
$config = $new_config;
$success = 1;
_info('Config ' . $config->ts . ' applied successfully');
}
else {
$success = 1;
_info('Config has not changed') unless $quiet;
}
};
if ( $EVAL_ERROR ) {
chomp $EVAL_ERROR;
_warn("Failed to apply config " . $new_config->ts
. ": $EVAL_ERROR Will try again.");
}
}
return ($config, $lib_dir, $new_daemon, $success);
}
sub apply_config {
my (%args) = @_;
@@ -5560,12 +5485,12 @@ sub apply_config {
agent
new_config
lib_dir
daemon_args
daemon
)) or die;
my $agent = $args{agent};
my $new_config = $args{new_config};
my $lib_dir = $args{lib_dir};
my $daemon_args = $args{daemon_args};
my $agent = $args{agent};
my $new_config = $args{new_config};
my $lib_dir = $args{lib_dir};
my $daemon = $args{daemon};
# Optional args
my $old_config = $args{old_config};
@@ -5597,8 +5522,8 @@ sub apply_config {
# does _not_ actually restart.
my $new_daemon;
my $make_new_daemon = 0;
my $old_pid = $daemon_args->{pid_file} || '';
my $old_log = $daemon_args->{log_file} || '';
my $old_pid = $daemon->{pid_file} || '';
my $old_log = $daemon->{log_file} || '';
my $new_pid = $new_config->options->{pid} || '';
my $new_log = $new_config->options->{log} || '';
if ( $old_pid ne $new_pid ) {
@@ -5606,7 +5531,7 @@ sub apply_config {
. ' to ' . ($new_pid || '(none)'));
$make_new_daemon = 1;
}
if ( $daemon_args->{daemonize} ) {
if ( $daemon->{daemonize} ) {
# --log only matters if we're daemonized
if ( $old_log ne $new_log ) {
_info('NOTICE: Changing --log file from ' . ($old_log || '(none)')
@@ -5623,14 +5548,12 @@ sub apply_config {
daemonize => 0,
pid_file => $new_pid,
log_file => $new_log,
force_log_file => $daemon_args->{daemonize},
force_log_file => $daemon->{daemonize},
);
eval {
$new_daemon->run();
$daemon_args->{pid_file} = $new_pid;
$daemon_args->{log_file} = $new_log;
if ( $daemon_args->{daemonize} && $old_log ne $new_log ) {
if ( $daemon->{daemonize} && $old_log ne $new_log ) {
_info('New log file, previous was ' . ($old_log || 'unset'));
}
if ( $old_pid eq $new_pid ) {
@@ -5641,7 +5564,7 @@ sub apply_config {
# the caller that they need to set old daemon pid_file_owner=0
# so it does not auto-remove the shared pid file when it goes
# away.
$daemon_args->{sharing_pid_file} = 1;
$new_daemon->{sharing_pid_file} = 1;
}
};
if ( $EVAL_ERROR ) {
@@ -5657,6 +5580,112 @@ sub apply_config {
return ($new_lib_dir || $lib_dir), $new_daemon;
}
# Write a Config resource to a Percona Toolkit config file,
# usually $HOME/pt-agent.conf.
sub write_config {
my (%args) = @_;
have_required_args(\%args, qw(
config
)) or die;
my $config = $args{config};
my $file = get_config_file();
_info("Writing config to $file");
# Get the api-key line if any; we don't want to/can't clobber this.
my $api_key;
if ( -f $file ) {
open my $fh, "<", $file
or die "Error opening $file: $OS_ERROR";
my $contents = do { local $/ = undef; <$fh> };
close $fh;
($api_key) = $contents =~ m/^(api-key=\S+)$/m;
}
# Re-write the api-key, if any, then write the config.
open my $fh, '>', $file
or die "Error opening $file: $OS_ERROR";
if ( $api_key ) {
print { $fh } $api_key, "\n"
or die "Error writing to $file: $OS_ERROR";
}
print { $fh } as_config($config)
or die "Error writing to $file: $OS_ERROR";
close $fh
or die "Error closing $file: $OS_ERROR";
return;
}
sub get_services {
my (%args) = @_;
have_required_args(\%args, qw(
link
agent
client
lib_dir
)) or die;
my $link = $args{link};
my $agent = $args{agent};
my $client = $args{client};
my $lib_dir = $args{lib_dir};
# Optional args
my $services = $args{services}; # may not be defined yet
my $success = 0;
eval {
_info('Getting services');
# Get services from Percona.
my $new_services = $client->get(
link => $link,
);
# If the current and new services are different,
# write the new ones to disk, then schedule them.
if ( resource_diff($services, $new_services) ) {
_info('New services');
write_services(
services => $new_services,
lib_dir => $lib_dir,
json => $args{json}, # optional, for testing
);
# TODO: this probably can't/won't fail, but if it does, is it
# worth setting success=0?
start_services(
services => $new_services,
lib_dir => $lib_dir,
bin_dir => $args{bin_dir}, # optional, for testing
);
schedule_services(
services => $new_services,
lib_dir => $lib_dir,
bin_dir => $args{bin_dir}, # optional, for testing
);
$services = $new_services;
$success = 1;
_info('Services updated successfully: '
. join(', ', map { $_->name } @$services));
}
else {
_info('Services have not changed');
}
};
if ( $EVAL_ERROR ) {
_warn($EVAL_ERROR);
}
return ($services, $success);
}
# Write each service to its own file in --lib/. Remove services
# that are not longer implemented (i.e. not in the services array).
sub write_services {
+101
View File
@@ -0,0 +1,101 @@
#!/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 File::Temp qw(tempdir);
use Percona::Test;
use Sandbox;
use Percona::Test::Mock::UserAgent;
require "$trunk/bin/pt-agent";
my $dp = new DSNParser(opts=>$dsn_opts);
my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
my $dbh = $sb->get_dbh_for('master');
my $dsn = $sb->dsn_for('master');
my $o = new OptionParser();
$o->get_specs("$trunk/bin/pt-agent");
$o->get_opts();
my $cxn = Cxn->new(
dsn_string => $dsn,
OptionParser => $o,
DSNParser => $dp,
);
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`;
}
my $tmp_lib = "/tmp/pt-agent";
my $tmp_log = "/tmp/pt-agent.log";
my $tmp_pid = "/tmp/pt-agent.pid";
diag(`rm -rf $tmp_lib`) if -d $tmp_lib;
unlink $tmp_log if -f $tmp_log;
unlink $tmp_pid if -f $tmp_pid;
my $config_file = pt_agent::get_config_file();
unlink $config_file if -f $config_file;
my $output;
{
no strict;
no warnings;
local *pt_agent::start_agent = sub {
print "start_agent\n";
return {
agent => 0,
client => 0,
daemon => 0,
};
};
local *pt_agent::run_agent = sub {
print "run_agent\n";
};
$output = output(
sub {
pt_agent::main(
qw(--api-key 123)
);
},
stderr => 1,
);
}
like(
$output,
qr/start_agent\nrun_agent\n/,
"Starts and runs without a config file"
);
# #############################################################################
# Done.
# #############################################################################
`crontab -r 2>/dev/null`;
if ( -f $config_file ) {
unlink $config_file
or warn "Error removing $config_file: $OS_ERROR";
}
done_testing;
+18 -74
View File
@@ -87,6 +87,10 @@ my $agent = Percona::WebAPI::Resource::Agent->new(
},
);
my $daemon = Daemon->new(
daemonzie => 0,
);
my @wait;
my $interval = sub {
my $t = shift;
@@ -138,22 +142,12 @@ my $svc0 = Percona::WebAPI::Resource::Service->new(
},
);
$ua->{responses}->{put} = [
{ # 1
headers => { 'Location' => '/agents/123' },
},
];
$ua->{responses}->{get} = [
{ # 2
headers => { 'X-Percona-Resource-Type' => 'Agent' },
content => as_hashref($agent, with_links => 1),
},
{ # 3
{
headers => { 'X-Percona-Resource-Type' => 'Config' },
content => as_hashref($config, with_links => 1),
},
{ # 4
{
headers => { 'X-Percona-Resource-Type' => 'Service' },
content => [ as_hashref($svc0, with_links => 1) ],
},
@@ -173,10 +167,6 @@ like(
my @ok_code = (); # callbacks
my @oktorun = (
1, # after get_api_client()
1, # after get_versions()
1, # init_agent() loop
1, # after init_agent()
1, # 1st main loop check
0, # 2nd main loop check
);
@@ -194,21 +184,15 @@ $output = output(
sub {
pt_agent::run_agent(
# Required args
api_key => '123',
agent => $agent,
client => $client,
daemon => $daemon,
interval => $interval,
lib_dir => $tmpdir,
Cxn => $cxn,
# Optional args, for testing
client => $client,
agent => $agent,
oktorun => $oktorun,
json => $json,
versions => {
Perl => '5.10.1',
},
entry_links => {
agents => '/agents',
},
);
},
stderr => 1,
@@ -269,17 +253,7 @@ like(
# Run run_agent again, like the agent had been stopped and restarted.
# #############################################################################
$ua->{responses}->{put} = [
{ # 1
headers => { 'Location' => '/agents/123' },
},
];
$ua->{responses}->{get} = [
{ # 2
headers => { 'X-Percona-Resource-Type' => 'Agent' },
content => as_hashref($agent, with_links => 1),
},
# First check, fail
{
code => 500,
@@ -308,10 +282,6 @@ $ua->{responses}->{get} = [
];
@oktorun = (
1, # after get_api_client()
1, # after get_versions()
1, # init_agent() loop
1, # after init_agent()
1, # 1st main loop check
# First check, error 500
1, # 2nd main loop check
@@ -325,7 +295,7 @@ $ua->{responses}->{get} = [
# query-history 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[6] = sub {
$ok_code[2] = sub {
unlink "$config_file";
unlink "$tmpdir/services/query-history";
Percona::Test::wait_until(sub { ! -f "$config_file" });
@@ -338,21 +308,15 @@ $output = output(
sub {
pt_agent::run_agent(
# Required args
api_key => '123',
agent => $agent,
client => $client,
daemon => $daemon,
interval => $interval,
lib_dir => $tmpdir,
Cxn => $cxn,
# Optional args, for testing
client => $client,
agent => $agent,
oktorun => $oktorun,
json => $json,
versions => {
Perl => '5.10.1',
},
entry_links => {
agents => '/agents',
},
);
},
stderr => 1,
@@ -430,17 +394,7 @@ $svc0 = Percona::WebAPI::Resource::Service->new(
},
);
$ua->{responses}->{put} = [
{ # 1
headers => { 'Location' => '/agents/123' },
},
];
$ua->{responses}->{get} = [
{ # 2
headers => { 'X-Percona-Resource-Type' => 'Agent' },
content => as_hashref($agent, with_links => 1),
},
{
headers => { 'X-Percona-Resource-Type' => 'Config' },
content => as_hashref($config, with_links => 1),
@@ -462,37 +416,27 @@ $ua->{responses}->{get} = [
@wait = ();
@ok_code = (); # callbacks
@oktorun = (
1, # after get_api_client()
1, # after get_versions()
1, # init_agent() loop
1, # after init_agent()
1, # 1st main loop check
# Run once
1, # 2nd main loop check
# Don't run it again
0, # 4th main loop check
0, # 3d main loop check
);
$output = output(
sub {
pt_agent::run_agent(
# Required args
api_key => '123',
agent => $agent,
client => $client,
daemon => $daemon,
interval => $interval,
lib_dir => $tmpdir,
Cxn => $cxn,
# Optional args, for testing
bin_dir => "$trunk/bin/",
client => $client,
agent => $agent,
oktorun => $oktorun,
json => $json,
versions => {
Perl => '5.10.1',
},
entry_links => {
agents => '/agents',
},
bin_dir => "$trunk/bin/",
);
},
stderr => 1,