From 0fa7aa4ddb0ca4fb8718a92295a10ce57b4b737a Mon Sep 17 00:00:00 2001 From: Daniel Nichter Date: Sat, 15 Jun 2013 17:06:44 -0700 Subject: [PATCH] Add --install-options, handle install on slave, use /etc/percona/agent/my.cnf if it exists. --- bin/pt-agent | 350 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 223 insertions(+), 127 deletions(-) diff --git a/bin/pt-agent b/bin/pt-agent index dcaf2a6a..06b7cfc3 100755 --- a/bin/pt-agent +++ b/bin/pt-agent @@ -5315,6 +5315,7 @@ sub main { OptionParser => $o, Cxn => $cxn, interactive => $o->get('interactive'), + flags => $o->get('install-options'), ); return $exit_status; } @@ -8042,9 +8043,11 @@ sub install { have_required_args(\%args, qw( OptionParser Cxn + flags )) or die; - my $o = $args{OptionParser}; - my $cxn = $args{Cxn}; + my $o = $args{OptionParser}; + my $cxn = $args{Cxn}; + my $flags = $args{flags}; # Optional args my $interactive = $args{interactive}; @@ -8054,12 +8057,16 @@ sub install { my $agent_my_cnf = '/etc/percona/agent/my.cnf'; my $config_file = get_config_file(); - my $stepno = 0; + my $step_result; + my $stepno = 0; + my $skip = 0; + my $step_fmt = "Step %d of %d: %s: "; my @steps = ( "Verify the user is root", "Check Perl module dependencies", - "Connect to MySQL", "Verify the API key", + "Connect to MySQL", + "Check if MySQL is a slave", "Create a MySQL user for the agent", "Initialize $agent_my_cnf", "Initialize $config_file", @@ -8072,9 +8079,25 @@ sub install { my (%args) = @_; my $repeat = $args{repeat}; my $done = $args{done}; - print "OK\n" if $stepno && !$repeat; + # Result of the previous step + my $result = 'OK'; + if ( $step_result ) { + $result = $step_result; + $step_result = undef; + } + print "$result\n" if $stepno && !$repeat; return if $done; - printf "Step %d of %d: %s: ", + while ( $skip ) { + printf $step_fmt, + $stepno + ($repeat ? 0 : 1), + $n_steps, + $steps[$repeat ? $stepno - 1 : $stepno]; + $stepno++; + print "SKIP\n"; + $skip--; + } + # This step + printf $step_fmt, $stepno + ($repeat ? 0 : 1), $n_steps, $steps[$repeat ? $stepno - 1 : $stepno]; @@ -8095,7 +8118,55 @@ sub install { $next_step->(); exit 1 if check_deps(); - # 2. Must be able to connect to MySQL to create pt_agent user. + # Must have a valid API key. + my $api_key = $o->get('api-key'); + my $client; + my $entry_links; + if ( $flags->{offline} ) { + $skip++; + } + else { + $next_step->(); + if ( !$api_key ) { + print "\n"; + if ( $interactive || -t STDIN ) { + while ( !$api_key ) { + print "Enter your API key: "; + $api_key = ; + chomp($api_key) if $api_key; + if ( !$api_key || length($api_key) < 32 ) { + warn "Invalid API key; it should be at least 32 characters long. Please try again.\n"; + $api_key = ''; + } + } + $next_step->(repeat => 1); # repeat + } + else { + die "Please specify your --api-key.\n"; + } + } + eval { + ($client, $entry_links) = get_api_client( + api_key => $api_key, + interval => sub { return; }, + tries => 1, + ); + }; + if ( my $e = $EVAL_ERROR ) { + die "Sorry, an error occurred while verifying the API key: $e"; + } + elsif ( !$entry_links ) { + if ( $client->response->code && $client->response->code == 403 ) { + die "Sorry, the API key $api_key is not valid. Please check the API key and try again.\n"; + } + else { + my $err = $client->response->message || 'Unknown error'; + die "Sorry, an error occured while verifying the API key: $err\n"; + } + } + } + + # Must be able to connect to MySQL to create pt_agent user. $next_step->(); eval { $cxn->connect(); @@ -8108,56 +8179,130 @@ sub install { . "with sufficient privileges to create MySQL users.\n"; } - # 3. Must have a valid API key. + # Check if MySQL is a slave $next_step->(); - my $api_key = $o->get('api-key'); - if ( !$api_key ) { - print "\n"; - if ( $interactive || -t STDIN ) { - while ( !$api_key ) { - print "Enter your API key: "; - $api_key = ; - chomp($api_key) if $api_key; - if ( !$api_key || length($api_key) < 32 ) { - warn "Invalid API key; it should be at least 32 characters long. Please try again.\n"; - $api_key = ''; - } - } - $next_step->(repeat => 1); # repeat - } - else { - die "Please specify your --api-key.\n"; - } + my $slave = $cxn->dbh->selectrow_hashref("SHOW SLAVE STATUS"); + if ( $slave ) { + $step_result = 'YES, TO MASTER ' . $slave->{master_host} || '?'; } - my $client; - my $entry_links; - eval { - ($client, $entry_links) = get_api_client( - api_key => $api_key, - interval => sub { return; }, - tries => 1, - ); - }; - if ( my $e = $EVAL_ERROR ) { - die "Sorry, an error occurred while verifying the API key: $e"; - } - elsif ( !$entry_links ) { - if ( $client->response->code && $client->response->code == 403 ) { - die "Sorry, the API key $api_key is not valid. Please check the API key and try again.\n"; - } - else { - my $err = $client->response->message || 'Unknown error'; - die "Sorry, an error occured while verifying the API key: $err\n"; - } + else { + $step_result = 'NO'; } # ######################################################################## # Do the install # ######################################################################## - # 4. Create/update the pt_agent MySQL user. pt-agent does not and should - # not run as a privileged MySQL user. + # Create a MySQL user for the agent $next_step->(); + if ( -f $agent_my_cnf ) { + $step_result = "NO, USE EXISTING $agent_my_cnf"; + } + else { + if ( !$slave ) { # master + create_mysql_user($cxn, $agent_my_cnf); + } + else { # slave + if ( $flags->{force_dangerous_slave_install} ) { + create_mysql_user($cxn, $agent_my_cnf); + } + else { + die "Sorry, cannot install the agent because MySQL is a slave " + . "and $agent_my_cnf does not exist. It is not safe to " + . "write to a slave, so a MySQL user for the agent cannot " + . "be created. First install the agent on the master, then " + . "copy $agent_my_cnf from the master server to this server. " + . "See --install-options for how to force a dangerous slave " + . "install.\n"; + } + } + } + + # Save the API key and defaults file in ~/.pt-agent.conf. + $next_step->(); + eval { + write_to_file( + data => "api-key=$api_key\ndefaults-file=$agent_my_cnf\n", + file => $config_file, + ); + }; + if ( $EVAL_ERROR ) { + die "Sorry, an error occured while initializing $config_file: " + . $EVAL_ERROR; + } + + # Init --lib and --spool. pt-agent would do this itself, but we'll + # do it now in case there are problems. + $next_step->(); + init_lib_dir( + lib_dir => $o->get('lib'), + ); + init_spool_dir( + spool_dir => $o->get('spool'), + ); + + # 8. Start the agent, don't run it yet. Normally this forks in + # anticipation of run_agent() being called next, but we don't do + # this during install; we run the agent manually later. + if ( $flags->{offline} ) { + $skip++; # Init agent + $skip++; # Run agent + } + else { + $next_step->(); + my $running = eval { + start_agent( + api_key => $api_key, + lib_dir => $o->get('lib'), + Cxn => $cxn, + client => $client, + entry_links => $entry_links, + agent_uuid => $o->get('agent-uuid'), + daemonize => 0, + pid_file => undef, + log_file => undef, + interval => sub { sleep 2; }, + tries => 2, + ); + }; + if ( $EVAL_ERROR ) { + die "Sorry, an error occurred while starting the agent: $EVAL_ERROR"; + } + + # 9. Run the agent daemon. If all the previous worked, the agent + # should be able to start without problems. It will get and apply + # the default config, then get and apply any services (probably won't + # have any yet). + $next_step->(); + my $env = env_vars(); + my $cmd = "$env $FindBin::Bin/pt-agent --daemonize"; + my $ret = system($cmd); + if ( $ret >> 8 ) { + die "Sorry, an error occured while starting pt-agent.\n"; + } + } + + # ######################################################################## + # Done installing + # ######################################################################## + $next_step->(done => 1); + + if ( $flags->{offline} ) { + } + else { + my $hostname = `hostname`; + chomp($hostname); + + print "\nThe agent has been installed and started, but it is not running any services yet. " + . "Go to https://cloud.percona.com/agents#$hostname to enable services for this agent.\n\n"; + } + + return; +} + +sub create_mysql_user { + my ($cxn, $agent_my_cnf) = @_; + my $random_pass = pseudo_random_password(); my $sql = "GRANT SUPER,USAGE ON *.* TO 'pt_agent'\@'localhost' " . "IDENTIFIED BY '$random_pass'"; @@ -8170,24 +8315,21 @@ sub install { } $cxn->dbh->disconnect(); - # 5. Save the pt_agent MySQL user info in /etc/percona/agent/my.cnf. + # Init $agent_my_cnf # We could set user= and pass= in ~/.pt-agent.conf, but each new agent # has a different MySQL password but shares the same default agent # config, so if we set pass=foo, the next agent would set it to # pass=bar, etc. Instead, every agent sets/uses # defaults-file=/etc/percona/agent/my.cnf in the default config, but # the contents of that file is different for each agent. - $next_step->(); + if ( !-d '/etc/percona' ) { _safe_mkdir('/etc/percona'); } if ( !-d '/etc/percona/agent' ) { _safe_mkdir('/etc/percona/agent'); } - my $my_cnf = "[client] -user=pt_agent -pass=$random_pass -"; + my $my_cnf = "[client]\nuser=pt_agent\npass=$random_pass\n"; my $dsn = $cxn->dsn; if ( $dsn->{h} ) { $my_cnf .= "host=$dsn->{h}\n"; @@ -8207,79 +8349,7 @@ pass=$random_pass if ( $EVAL_ERROR ) { die "Sorry, an error occured while initializing $agent_my_cnf: " . $EVAL_ERROR; - } - - # 6. Save the API key and defaults file in ~/.pt-agent.conf. - $next_step->(); - eval { - write_to_file( - data => -"api-key=$api_key -defaults-file=$agent_my_cnf -", - file => $config_file, - ); - }; - if ( $EVAL_ERROR ) { - die "Sorry, an error occured while initializing $config_file: " - . $EVAL_ERROR; - } - - # 7. Init --lib and --spool. pt-agent would do this itself, but we'll - # do it now in case there are problems. - $next_step->(); - init_lib_dir( - lib_dir => $o->get('lib'), - ); - init_spool_dir( - spool_dir => $o->get('spool'), - ); - - # 8. Start the agent, don't run it yet. Normally this forks in - # anticipation of run_agent() being called next, but we don't do - # this during install; we run the agent manually later. - $next_step->(); - my $running = eval { - start_agent( - api_key => $api_key, - lib_dir => $o->get('lib'), - Cxn => $cxn, - client => $client, - entry_links => $entry_links, - agent_uuid => $o->get('agent-uuid'), - daemonize => 0, - pid_file => undef, - log_file => undef, - interval => sub { sleep 2; }, - tries => 2, - ); - }; - if ( $EVAL_ERROR ) { - die "Sorry, an error occurred while starting the agent: $EVAL_ERROR"; - } - - # 9. Run the agent daemon. If all the previous worked, the agent - # should be able to start without problems. It will get and apply - # the default config, then get and apply any services (probably won't - # have any yet). - $next_step->(); - my $env = env_vars(); - my $cmd = "$env $FindBin::Bin/pt-agent --daemonize"; - my $ret = system($cmd); - if ( $ret >> 8 ) { - die "Sorry, an error occured while starting pt-agent.\n"; - } - - # ######################################################################## - # Done installing - # ######################################################################## - $next_step->(done => 1); - - my $hostname = `hostname`; - chomp($hostname); - - print "\nThe agent has been installed and started, but it is not running any services yet. " - . "Go to https://cloud.percona.com/agents#$hostname to enable services for this agent.\n\n"; + } return; } @@ -8733,6 +8803,32 @@ MySQL host. Install pt-agent as root. +=item --install-options + +type: Hash + +Comma-separated list of L<"--install"> options. Options are: + +=over + +=item offline + +Do not verify the API key or start the agent. + +=item force_dangerous_slave_install + +Like the option's name suggests: this forces a dangerous slave install, +so you should not use this option unless you are aware of the potential +consequences. To install the agent on a slave, C +must exist because it is not safe to create the agent's MySQL user on +a slave. The agent should be installed on the master first, then +C copied from the master server to the slave +server. Using this option forces the agent to create the agent's MySQL +user on the slave. B: writing to a slave is dangerous and could +cause replication to crash. + +=back + =item --interactive Run in interactive mode (disables L<"--[no]log-api">).