Merge lp:~percona-toolkit-dev/percona-toolkit/pt-agent-fixes into lp:~percona-toolkit-dev/percona-toolkit/release-2.2.6
- pt-agent-fixes
- Merge into release-2.2.6
Proposed by
Daniel Nichter
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
- 600. By Daniel Nichter
-
Fix typo causing bug 1251726: --uninstall crashes.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel Nichter (daniel-nichter) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-agent' | |||
2 | --- bin/pt-agent 2013-11-08 03:00:56 +0000 | |||
3 | +++ bin/pt-agent 2013-12-11 04:33:49 +0000 | |||
4 | @@ -3691,7 +3691,7 @@ | |||
5 | 3691 | 3691 | ||
6 | 3692 | sub connect { | 3692 | sub connect { |
7 | 3693 | my ( $self, %opts ) = @_; | 3693 | my ( $self, %opts ) = @_; |
9 | 3694 | my $dsn = $self->{dsn}; | 3694 | my $dsn = $opts{dsn} || $self->{dsn}; |
10 | 3695 | my $dp = $self->{DSNParser}; | 3695 | my $dp = $self->{DSNParser}; |
11 | 3696 | 3696 | ||
12 | 3697 | my $dbh = $self->{dbh}; | 3697 | my $dbh = $self->{dbh}; |
13 | @@ -3710,6 +3710,13 @@ | |||
14 | 3710 | } | 3710 | } |
15 | 3711 | 3711 | ||
16 | 3712 | $dbh = $self->set_dbh($dbh); | 3712 | $dbh = $self->set_dbh($dbh); |
17 | 3713 | if ( $opts{dsn} ) { | ||
18 | 3714 | $self->{dsn} = $dsn; | ||
19 | 3715 | $self->{dsn_name} = $dp->as_string($dsn, [qw(h P S)]) | ||
20 | 3716 | || $dp->as_string($dsn, [qw(F)]) | ||
21 | 3717 | || ''; | ||
22 | 3718 | |||
23 | 3719 | } | ||
24 | 3713 | PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name}); | 3720 | PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name}); |
25 | 3714 | return $dbh; | 3721 | return $dbh; |
26 | 3715 | } | 3722 | } |
27 | @@ -3873,6 +3880,8 @@ | |||
28 | 3873 | return $val if $val =~ m/^0x[0-9a-fA-F]+$/ # quote hex data | 3880 | return $val if $val =~ m/^0x[0-9a-fA-F]+$/ # quote hex data |
29 | 3874 | && !$args{is_char}; # unless is_char is true | 3881 | && !$args{is_char}; # unless is_char is true |
30 | 3875 | 3882 | ||
31 | 3883 | return $val if $args{is_float}; | ||
32 | 3884 | |||
33 | 3876 | $val =~ s/(['\\])/\\$1/g; | 3885 | $val =~ s/(['\\])/\\$1/g; |
34 | 3877 | return "'$val'"; | 3886 | return "'$val'"; |
35 | 3878 | } | 3887 | } |
36 | @@ -5092,7 +5101,7 @@ | |||
37 | 5092 | sub debug { | 5101 | sub debug { |
38 | 5093 | my $self = shift; | 5102 | my $self = shift; |
39 | 5094 | return if $self->online_logging; | 5103 | return if $self->online_logging; |
41 | 5095 | return $self->_log(0, 'DEBUG', 1, @_); | 5104 | return $self->_log(0, 'DEBUG', @_); |
42 | 5096 | } | 5105 | } |
43 | 5097 | 5106 | ||
44 | 5098 | sub info { | 5107 | sub info { |
45 | @@ -5128,7 +5137,7 @@ | |||
46 | 5128 | } | 5137 | } |
47 | 5129 | 5138 | ||
48 | 5130 | sub _log { | 5139 | sub _log { |
50 | 5131 | my ($self, $online, $level, $msg, $offline) = @_; | 5140 | my ($self, $online, $level, $msg) = @_; |
51 | 5132 | 5141 | ||
52 | 5133 | my $ts = ts(time, 1); # 1=UTC | 5142 | my $ts = ts(time, 1); # 1=UTC |
53 | 5134 | my $level_number = level_number($level); | 5143 | my $level_number = level_number($level); |
54 | @@ -5792,6 +5801,7 @@ | |||
55 | 5792 | # Optional args | 5801 | # Optional args |
56 | 5793 | my $_oktorun = $args{oktorun} || sub { return $oktorun }; | 5802 | my $_oktorun = $args{oktorun} || sub { return $oktorun }; |
57 | 5794 | my $actions = $args{actions}; | 5803 | my $actions = $args{actions}; |
58 | 5804 | my $quiet = $args{quiet}; | ||
59 | 5795 | 5805 | ||
60 | 5796 | # Update these attribs every time the agent is initialized. | 5806 | # Update these attribs every time the agent is initialized. |
61 | 5797 | # Other optional attribs, like versions, are left to the caller. | 5807 | # Other optional attribs, like versions, are left to the caller. |
62 | @@ -5802,7 +5812,7 @@ | |||
63 | 5802 | # Try to create/update the Agent. | 5812 | # Try to create/update the Agent. |
64 | 5803 | my $success = 0; | 5813 | my $success = 0; |
65 | 5804 | while ( $_oktorun->() && $tries-- ) { | 5814 | while ( $_oktorun->() && $tries-- ) { |
67 | 5805 | if ( !$state->{init_action}++ ) { | 5815 | if ( !$state->{init_action}++ && !$quiet ) { |
68 | 5806 | $logger->info($action eq 'put' ? "Updating agent " . $agent->name | 5816 | $logger->info($action eq 'put' ? "Updating agent " . $agent->name |
69 | 5807 | : "Creating new agent"); | 5817 | : "Creating new agent"); |
70 | 5808 | } | 5818 | } |
71 | @@ -5841,7 +5851,7 @@ | |||
72 | 5841 | } | 5851 | } |
73 | 5842 | } | 5852 | } |
74 | 5843 | elsif ( !$agent_uri ) { | 5853 | elsif ( !$agent_uri ) { |
76 | 5844 | $logger->info("No URI for Agent " . $agent->name); | 5854 | $logger->warning("No URI for Agent " . $agent->name); |
77 | 5845 | } | 5855 | } |
78 | 5846 | else { | 5856 | else { |
79 | 5847 | # The Agent URI will have been returned in the Location header | 5857 | # The Agent URI will have been returned in the Location header |
80 | @@ -5868,7 +5878,7 @@ | |||
81 | 5868 | delete $state->{init_action}; | 5878 | delete $state->{init_action}; |
82 | 5869 | delete $state->{too_many_agents}; | 5879 | delete $state->{too_many_agents}; |
83 | 5870 | 5880 | ||
85 | 5871 | if ( $agent && $success ) { | 5881 | if ( $agent && $success && !$quiet ) { |
86 | 5872 | $logger->info("Agent " . $agent->name . " (" . $agent->uuid . ") is ready"); | 5882 | $logger->info("Agent " . $agent->name . " (" . $agent->uuid . ") is ready"); |
87 | 5873 | } | 5883 | } |
88 | 5874 | 5884 | ||
89 | @@ -5955,7 +5965,7 @@ | |||
90 | 5955 | my $entry_links = $args{entry_links}; # for testing | 5965 | my $entry_links = $args{entry_links}; # for testing |
91 | 5956 | my $logger_client = $args{logger_client}; # for testing | 5966 | my $logger_client = $args{logger_client}; # for testing |
92 | 5957 | 5967 | ||
94 | 5958 | $logger->info('Starting agent'); | 5968 | # $logger->info('Starting agent'); |
95 | 5959 | 5969 | ||
96 | 5960 | # Daemonize first so all output goes to the --log. | 5970 | # Daemonize first so all output goes to the --log. |
97 | 5961 | my $daemon = Daemon->new( | 5971 | my $daemon = Daemon->new( |
98 | @@ -6126,8 +6136,9 @@ | |||
99 | 6126 | # ####################################################################### | 6136 | # ####################################################################### |
100 | 6127 | # Main agent loop | 6137 | # Main agent loop |
101 | 6128 | # ####################################################################### | 6138 | # ####################################################################### |
102 | 6139 | $state->{need_mysql_version} = 1; | ||
103 | 6129 | $state->{first_config} = 1; | 6140 | $state->{first_config} = 1; |
105 | 6130 | my $first_config_interval = 60; | 6141 | my $first_config_interval = 20; |
106 | 6131 | $logger->info("Checking silently every $first_config_interval seconds" | 6142 | $logger->info("Checking silently every $first_config_interval seconds" |
107 | 6132 | . " for the first config"); | 6143 | . " for the first config"); |
108 | 6133 | 6144 | ||
109 | @@ -6136,38 +6147,6 @@ | |||
110 | 6136 | my $config; | 6147 | my $config; |
111 | 6137 | my $services = {}; | 6148 | my $services = {}; |
112 | 6138 | while ( $_oktorun->() ) { | 6149 | while ( $_oktorun->() ) { |
113 | 6139 | check_if_mysql_restarted( | ||
114 | 6140 | Cxn => $cxn, | ||
115 | 6141 | ); | ||
116 | 6142 | |||
117 | 6143 | if ( $state->{need_mysql_version} ) { | ||
118 | 6144 | my $versions = get_versions( | ||
119 | 6145 | Cxn => $cxn, | ||
120 | 6146 | ); | ||
121 | 6147 | if ( $versions->{MySQL} ) { | ||
122 | 6148 | $agent->versions($versions); | ||
123 | 6149 | my $updated_agent; | ||
124 | 6150 | ($agent, $updated_agent) = init_agent( | ||
125 | 6151 | agent => $agent, | ||
126 | 6152 | action => 'put', | ||
127 | 6153 | link => $agent->links->{self}, | ||
128 | 6154 | client => $client, | ||
129 | 6155 | interval => sub { return; }, | ||
130 | 6156 | tries => 1, # optional | ||
131 | 6157 | ); | ||
132 | 6158 | if ( $updated_agent ) { | ||
133 | 6159 | $logger->info("Got MySQL versions"); | ||
134 | 6160 | save_agent( | ||
135 | 6161 | agent => $agent, | ||
136 | 6162 | lib_dir => $lib_dir, | ||
137 | 6163 | ); | ||
138 | 6164 | } | ||
139 | 6165 | else { | ||
140 | 6166 | $state->{need_mysql_version} = 1; | ||
141 | 6167 | } | ||
142 | 6168 | } | ||
143 | 6169 | } | ||
144 | 6170 | |||
145 | 6171 | ($config, $lib_dir, $new_daemon, $success) = get_config( | 6150 | ($config, $lib_dir, $new_daemon, $success) = get_config( |
146 | 6172 | link => $agent->links->{config}, | 6151 | link => $agent->links->{config}, |
147 | 6173 | agent => $agent, | 6152 | agent => $agent, |
148 | @@ -6185,6 +6164,7 @@ | |||
149 | 6185 | delete $state->{first_config}; | 6164 | delete $state->{first_config}; |
150 | 6186 | $logger->info('Agent has been configured'); | 6165 | $logger->info('Agent has been configured'); |
151 | 6187 | } | 6166 | } |
152 | 6167 | |||
153 | 6188 | if ( $new_daemon ) { | 6168 | if ( $new_daemon ) { |
154 | 6189 | # NOTE: Daemon objects use DESTROY to auto-remove their pid file | 6169 | # NOTE: Daemon objects use DESTROY to auto-remove their pid file |
155 | 6190 | # when they lose scope (i.e. ref count goes to zero). This | 6170 | # when they lose scope (i.e. ref count goes to zero). This |
156 | @@ -6200,6 +6180,60 @@ | |||
157 | 6200 | $daemon = $new_daemon; | 6180 | $daemon = $new_daemon; |
158 | 6201 | } | 6181 | } |
159 | 6202 | 6182 | ||
160 | 6183 | # Connect to MySQL, then check stuff. | ||
161 | 6184 | my $o = new OptionParser(); | ||
162 | 6185 | $o->get_specs(); | ||
163 | 6186 | $o->get_opts(); | ||
164 | 6187 | my $dp = $o->DSNParser(); | ||
165 | 6188 | $dp->prop('set-vars', $o->set_vars()); | ||
166 | 6189 | my $dsn = $dp->parse_options($o); | ||
167 | 6190 | eval { | ||
168 | 6191 | $cxn->connect(dsn => $dsn); | ||
169 | 6192 | }; | ||
170 | 6193 | if ( $EVAL_ERROR ) { | ||
171 | 6194 | $logger->warning("MySQL connection failure: $EVAL_ERROR"); | ||
172 | 6195 | $state->{have_mysql} = 0; | ||
173 | 6196 | $state->{need_mysql_version} = 1; | ||
174 | 6197 | } | ||
175 | 6198 | else { | ||
176 | 6199 | if ( !$state->{have_mysql} ) { | ||
177 | 6200 | $logger->info("MySQL connection OK"); | ||
178 | 6201 | } | ||
179 | 6202 | $state->{have_mysql} = 1; | ||
180 | 6203 | check_if_mysql_restarted( | ||
181 | 6204 | dbh => $cxn->dbh, | ||
182 | 6205 | ); | ||
183 | 6206 | if ( $state->{need_mysql_version} ) { | ||
184 | 6207 | $logger->debug("Need MySQL version"); | ||
185 | 6208 | my $versions = get_versions(Cxn => $cxn); | ||
186 | 6209 | if ( $versions->{MySQL} ) { | ||
187 | 6210 | $agent->versions($versions); | ||
188 | 6211 | my $updated_agent; | ||
189 | 6212 | ($agent, $updated_agent) = init_agent( | ||
190 | 6213 | agent => $agent, | ||
191 | 6214 | action => 'put', | ||
192 | 6215 | link => $agent->links->{self}, | ||
193 | 6216 | client => $client, | ||
194 | 6217 | tries => 1, | ||
195 | 6218 | interval => sub { return; }, | ||
196 | 6219 | quiet => 1, | ||
197 | 6220 | ); | ||
198 | 6221 | if ( $updated_agent ) { | ||
199 | 6222 | $logger->debug("Got MySQL version"); | ||
200 | 6223 | save_agent( | ||
201 | 6224 | agent => $agent, | ||
202 | 6225 | lib_dir => $lib_dir, | ||
203 | 6226 | ); | ||
204 | 6227 | delete $state->{need_mysql_version}; | ||
205 | 6228 | } | ||
206 | 6229 | } | ||
207 | 6230 | else { | ||
208 | 6231 | $logger->debug("Failed to get MySQL version"); | ||
209 | 6232 | } | ||
210 | 6233 | } | ||
211 | 6234 | $cxn->dbh->disconnect(); | ||
212 | 6235 | } | ||
213 | 6236 | |||
214 | 6203 | # Check the safeguards. | 6237 | # Check the safeguards. |
215 | 6204 | my ($disk_space, $disk_space_ok); | 6238 | my ($disk_space, $disk_space_ok); |
216 | 6205 | eval { | 6239 | eval { |
217 | @@ -6341,6 +6375,8 @@ | |||
218 | 6341 | $config = $new_config; | 6375 | $config = $new_config; |
219 | 6342 | $success = 1; | 6376 | $success = 1; |
220 | 6343 | $logger->info('Config ' . $config->ts . ' applied'); | 6377 | $logger->info('Config ' . $config->ts . ' applied'); |
221 | 6378 | |||
222 | 6379 | $state->{need_mysql_version} = 1; | ||
223 | 6344 | } | 6380 | } |
224 | 6345 | else { | 6381 | else { |
225 | 6346 | $success = 1; | 6382 | $success = 1; |
226 | @@ -8319,7 +8355,7 @@ | |||
227 | 8319 | ); | 8355 | ); |
228 | 8320 | } | 8356 | } |
229 | 8321 | # Match the first digits, which should be the PID. | 8357 | # Match the first digits, which should be the PID. |
231 | 8322 | ($pid) =~ $ps_output =~ m/(\d+)/; | 8358 | ($pid) = $ps_output =~ m/(\d+)/; |
232 | 8323 | } | 8359 | } |
233 | 8324 | 8360 | ||
234 | 8325 | if ( !$pid ) { | 8361 | if ( !$pid ) { |
235 | @@ -8405,6 +8441,7 @@ | |||
236 | 8405 | 8441 | ||
237 | 8406 | my $agent_my_cnf = '/etc/percona/agent/my.cnf'; | 8442 | my $agent_my_cnf = '/etc/percona/agent/my.cnf'; |
238 | 8407 | my $config_file = get_config_file(); | 8443 | my $config_file = get_config_file(); |
239 | 8444 | my $lib_dir = $o->get('lib'); | ||
240 | 8408 | 8445 | ||
241 | 8409 | my $step_result; | 8446 | my $step_result; |
242 | 8410 | my $stepno = 0; | 8447 | my $stepno = 0; |
243 | @@ -8414,6 +8451,7 @@ | |||
244 | 8414 | "Verify the user is root", | 8451 | "Verify the user is root", |
245 | 8415 | "Check Perl module dependencies", | 8452 | "Check Perl module dependencies", |
246 | 8416 | "Check for crontab", | 8453 | "Check for crontab", |
247 | 8454 | "Verify pt-agent is not installed", | ||
248 | 8417 | "Verify the API key", | 8455 | "Verify the API key", |
249 | 8418 | "Connect to MySQL", | 8456 | "Connect to MySQL", |
250 | 8419 | "Check if MySQL is a slave", | 8457 | "Check if MySQL is a slave", |
251 | @@ -8483,7 +8521,25 @@ | |||
252 | 8483 | die "cron is not installed, or crontab is not in your PATH.\n"; | 8521 | die "cron is not installed, or crontab is not in your PATH.\n"; |
253 | 8484 | } | 8522 | } |
254 | 8485 | 8523 | ||
255 | 8524 | # Verify pt-agent is not installed | ||
256 | 8525 | $next_step->(); | ||
257 | 8526 | my @install_files = ($agent_my_cnf, $config_file, "$lib_dir/agent"); | ||
258 | 8527 | my @have_files; | ||
259 | 8528 | foreach my $file (@install_files) { | ||
260 | 8529 | push @have_files, $file if -f $file; | ||
261 | 8530 | } | ||
262 | 8531 | if ( scalar @have_files ) { | ||
263 | 8532 | print "FAIL\n"; | ||
264 | 8533 | die "It looks like pt-agent is already installed because these files exist:\n" | ||
265 | 8534 | . join("\n", map { " $_" } @have_files) | ||
266 | 8535 | . "\nRun pt-agent --uninstall to remove these files. To upgrade pt-agent, " | ||
267 | 8536 | . "install the new version, run pt-agent --stop, then pt-agent --daemonize " | ||
268 | 8537 | . "to restart pt-agent with the new version.\n"; | ||
269 | 8538 | } | ||
270 | 8539 | |||
271 | 8486 | # Must have a valid API key. | 8540 | # Must have a valid API key. |
272 | 8541 | $next_step->(); | ||
273 | 8542 | my $got_api_key = 0; | ||
274 | 8487 | my $api_key = $o->get('api-key'); | 8543 | my $api_key = $o->get('api-key'); |
275 | 8488 | if ( !$api_key ) { | 8544 | if ( !$api_key ) { |
276 | 8489 | print "\n"; | 8545 | print "\n"; |
277 | @@ -8497,19 +8553,22 @@ | |||
278 | 8497 | $api_key = ''; | 8553 | $api_key = ''; |
279 | 8498 | } | 8554 | } |
280 | 8499 | } | 8555 | } |
281 | 8500 | $next_step->(repeat => 1); # repeat | ||
282 | 8501 | } | 8556 | } |
283 | 8502 | else { | 8557 | else { |
284 | 8503 | die "Please specify your --api-key.\n"; | 8558 | die "Please specify your --api-key.\n"; |
285 | 8504 | } | 8559 | } |
286 | 8560 | $got_api_key = 1; | ||
287 | 8505 | } | 8561 | } |
288 | 8562 | |||
289 | 8506 | my $client; | 8563 | my $client; |
290 | 8507 | my $entry_links; | 8564 | my $entry_links; |
291 | 8508 | if ( $flags->{offline} ) { | 8565 | if ( $flags->{offline} ) { |
292 | 8509 | $skip++; | 8566 | $skip++; |
293 | 8510 | } | 8567 | } |
294 | 8511 | else { | 8568 | else { |
296 | 8512 | $next_step->(); | 8569 | if ($got_api_key) { |
297 | 8570 | $next_step->(repeat => 1); | ||
298 | 8571 | } | ||
299 | 8513 | eval { | 8572 | eval { |
300 | 8514 | ($client, $entry_links) = get_api_client( | 8573 | ($client, $entry_links) = get_api_client( |
301 | 8515 | api_key => $api_key, | 8574 | api_key => $api_key, |
302 | @@ -8571,30 +8630,13 @@ | |||
303 | 8571 | if ( $flags->{force_dangerous_slave_install} ) { | 8630 | if ( $flags->{force_dangerous_slave_install} ) { |
304 | 8572 | create_mysql_user($cxn, $agent_my_cnf); | 8631 | create_mysql_user($cxn, $agent_my_cnf); |
305 | 8573 | } | 8632 | } |
306 | 8574 | elsif ( $interactive || -t STDIN ) { | ||
307 | 8575 | print "\nMySQL is a slave and $agent_my_cnf does not exist. " | ||
308 | 8576 | . "To install the agent, please enter the MySQL username and " | ||
309 | 8577 | . "password to use. The MySQL user must have SUPER and USAGE " | ||
310 | 8578 | . "privileges on all databases, for example: " | ||
311 | 8579 | . "GRANT SUPER,USAGE ON *.* TO pt_agent'\@'localhost'. " | ||
312 | 8580 | . "If the agent has been installed on the master, you can use " | ||
313 | 8581 | . "the MySQL username and password in $agent_my_cnf on the " | ||
314 | 8582 | . "master. CTRL-C to abort install.\n"; | ||
315 | 8583 | print "MySQL username: "; | ||
316 | 8584 | my $user = <STDIN>; | ||
317 | 8585 | chomp($user) if $user; | ||
318 | 8586 | my $pass = OptionParser::prompt_noecho("MySQL password: "); | ||
319 | 8587 | create_mysql_user($cxn, $agent_my_cnf, $user, $pass); | ||
320 | 8588 | $next_step->(repeat => 1); # repeat | ||
321 | 8589 | } | ||
322 | 8590 | else { | 8633 | else { |
323 | 8591 | die "Sorry, cannot install the agent because MySQL is a slave " | 8634 | die "Sorry, cannot install the agent because MySQL is a slave " |
324 | 8592 | . "and $agent_my_cnf does not exist. It is not safe to " | 8635 | . "and $agent_my_cnf does not exist. It is not safe to " |
325 | 8593 | . "write to a slave, so a MySQL user for the agent cannot " | 8636 | . "write to a slave, so a MySQL user for the agent cannot " |
326 | 8594 | . "be created. First install the agent on the master, then " | 8637 | . "be created. First install the agent on the master, then " |
327 | 8595 | . "copy $agent_my_cnf from the master to this server. " | 8638 | . "copy $agent_my_cnf from the master to this server. " |
330 | 8596 | . "See --install-options for how to force a dangerous slave " | 8639 | . "See SLAVE INSTALL in the docs for more information.\n"; |
329 | 8597 | . "install.\n"; | ||
331 | 8598 | } | 8640 | } |
332 | 8599 | } | 8641 | } |
333 | 8600 | } | 8642 | } |
334 | @@ -8616,7 +8658,7 @@ | |||
335 | 8616 | # do it now in case there are problems. | 8658 | # do it now in case there are problems. |
336 | 8617 | $next_step->(); | 8659 | $next_step->(); |
337 | 8618 | init_lib_dir( | 8660 | init_lib_dir( |
339 | 8619 | lib_dir => $o->get('lib'), | 8661 | lib_dir => $lib_dir, |
340 | 8620 | ); | 8662 | ); |
341 | 8621 | init_spool_dir( | 8663 | init_spool_dir( |
342 | 8622 | spool_dir => $o->get('spool'), | 8664 | spool_dir => $o->get('spool'), |
343 | @@ -8997,33 +9039,7 @@ | |||
344 | 8997 | my (%args) = @_; | 9039 | my (%args) = @_; |
345 | 8998 | my $cxn = $args{Cxn}; | 9040 | my $cxn = $args{Cxn}; |
346 | 8999 | my $tries = $args{tries} || 1; | 9041 | my $tries = $args{tries} || 1; |
374 | 9000 | my $interval = $args{interval} || sub { sleep 3; }; | 9042 | my $interval = $args{interval} || sub { return; }; |
348 | 9001 | |||
349 | 9002 | my $have_mysql = 0; | ||
350 | 9003 | if ( $cxn ) { | ||
351 | 9004 | $logger->debug("Connecting to MySQL"); | ||
352 | 9005 | foreach my $tryno ( 1..$tries ) { | ||
353 | 9006 | eval { | ||
354 | 9007 | $cxn->connect(); | ||
355 | 9008 | }; | ||
356 | 9009 | if ( $EVAL_ERROR ) { | ||
357 | 9010 | $logger->debug("Cannot connect to MySQL: $EVAL_ERROR"); | ||
358 | 9011 | } | ||
359 | 9012 | else { | ||
360 | 9013 | $have_mysql = 1; | ||
361 | 9014 | delete $state->{need_mysql_version}; | ||
362 | 9015 | last; # success | ||
363 | 9016 | } | ||
364 | 9017 | if ( $tryno < $tries ) { | ||
365 | 9018 | sleep $interval; # failure, try again | ||
366 | 9019 | } | ||
367 | 9020 | else { | ||
368 | 9021 | $state->{need_mysql_version} = 1; | ||
369 | 9022 | $logger->warning("Cannot get MySQL version, will try again later"); | ||
370 | 9023 | last; # failure | ||
371 | 9024 | } | ||
372 | 9025 | } | ||
373 | 9026 | } | ||
375 | 9027 | 9043 | ||
376 | 9028 | # This is currently the actual response from GET v.percona.com | 9044 | # This is currently the actual response from GET v.percona.com |
377 | 9029 | my $fake_response = <<EOL; | 9045 | my $fake_response = <<EOL; |
378 | @@ -9032,6 +9048,10 @@ | |||
379 | 9032 | Perl;perl_version | 9048 | Perl;perl_version |
380 | 9033 | DBD::mysql;perl_module_version | 9049 | DBD::mysql;perl_module_version |
381 | 9034 | Percona::Toolkit;perl_module_version | 9050 | Percona::Toolkit;perl_module_version |
382 | 9051 | JSON;perl_module_version | ||
383 | 9052 | LWP;perl_module_version | ||
384 | 9053 | IO::Socket::SSL;perl_module_version | ||
385 | 9054 | DBD::mysql;perl_module_version | ||
386 | 9035 | EOL | 9055 | EOL |
387 | 9036 | 9056 | ||
388 | 9037 | my $items = VersionCheck::parse_server_response( | 9057 | my $items = VersionCheck::parse_server_response( |
389 | @@ -9042,12 +9062,39 @@ | |||
390 | 9042 | { name => 'system', id => 0, }, | 9062 | { name => 'system', id => 0, }, |
391 | 9043 | ]; | 9063 | ]; |
392 | 9044 | 9064 | ||
393 | 9065 | my $have_mysql = -1; | ||
394 | 9066 | if ( !$cxn->dbh || !$cxn->dbh->ping() ) { | ||
395 | 9067 | $logger->debug("Connecting to MySQL"); | ||
396 | 9068 | eval { | ||
397 | 9069 | $cxn->connect(); | ||
398 | 9070 | }; | ||
399 | 9071 | if ( $EVAL_ERROR ) { | ||
400 | 9072 | $logger->debug("Cannot connect to MySQL: $EVAL_ERROR"); | ||
401 | 9073 | $have_mysql = 0; | ||
402 | 9074 | } | ||
403 | 9075 | else { | ||
404 | 9076 | $have_mysql = 1; | ||
405 | 9077 | } | ||
406 | 9078 | } | ||
407 | 9079 | |||
408 | 9045 | if ( $have_mysql ) { | 9080 | if ( $have_mysql ) { |
409 | 9081 | $logger->debug("Have MySQL connection"); | ||
410 | 9046 | my ($name, $id) = VersionCheck::get_instance_id( | 9082 | my ($name, $id) = VersionCheck::get_instance_id( |
411 | 9047 | { dbh => $cxn->dbh, dsn => $cxn->dsn }, | 9083 | { dbh => $cxn->dbh, dsn => $cxn->dsn }, |
412 | 9048 | ); | 9084 | ); |
413 | 9049 | push @$instances, | 9085 | push @$instances, |
414 | 9050 | { name => $name, id => $id, dbh => $cxn->dbh, dsn => $cxn->dsn }; | 9086 | { name => $name, id => $id, dbh => $cxn->dbh, dsn => $cxn->dsn }; |
415 | 9087 | |||
416 | 9088 | # Disconnect MySQL if we connected it. | ||
417 | 9089 | if ( $have_mysql == 1 ) { | ||
418 | 9090 | $logger->debug("Disconnecting MySQL"); | ||
419 | 9091 | eval { | ||
420 | 9092 | $cxn->dbh->disconnect(); | ||
421 | 9093 | }; | ||
422 | 9094 | if ( $EVAL_ERROR ) { | ||
423 | 9095 | $logger->debug($EVAL_ERROR); | ||
424 | 9096 | } | ||
425 | 9097 | } | ||
426 | 9051 | } | 9098 | } |
427 | 9052 | 9099 | ||
428 | 9053 | my $versions = VersionCheck::get_versions( | 9100 | my $versions = VersionCheck::get_versions( |
429 | @@ -9112,47 +9159,21 @@ | |||
430 | 9112 | sub check_if_mysql_restarted { | 9159 | sub check_if_mysql_restarted { |
431 | 9113 | my (%args) = @_; | 9160 | my (%args) = @_; |
432 | 9114 | have_required_args(\%args, qw( | 9161 | have_required_args(\%args, qw( |
434 | 9115 | Cxn | 9162 | dbh |
435 | 9116 | )) or die; | 9163 | )) or die; |
437 | 9117 | my $cxn = $args{Cxn}; | 9164 | my $dbh = $args{dbh}; |
438 | 9118 | 9165 | ||
439 | 9119 | # Optional args | 9166 | # Optional args |
440 | 9120 | my $uptime = $args{uptime}; # for testing | 9167 | my $uptime = $args{uptime}; # for testing |
441 | 9121 | my $margin = $args{margin} || 5; | 9168 | my $margin = $args{margin} || 5; |
442 | 9122 | 9169 | ||
443 | 9123 | if ( !$uptime ) { | 9170 | if ( !$uptime ) { |
476 | 9124 | $logger->debug("Connecting to MySQL"); | 9171 | my $sql = "SHOW STATUS LIKE 'uptime'"; |
477 | 9125 | my $t0 = time; | 9172 | eval { |
478 | 9126 | my $e; | 9173 | (undef, $uptime) = $dbh->selectrow_array($sql); |
479 | 9127 | my $tries = 2; | 9174 | }; |
480 | 9128 | my $have_mysql = 0; | 9175 | if ( $EVAL_ERROR ) { |
481 | 9129 | TRY: | 9176 | $logger->error("$sql: $EVAL_ERROR"); |
450 | 9130 | foreach my $tryno ( 1..$tries ) { | ||
451 | 9131 | eval { | ||
452 | 9132 | $cxn->connect(); | ||
453 | 9133 | }; | ||
454 | 9134 | $e = $EVAL_ERROR; | ||
455 | 9135 | if ( $e ) { | ||
456 | 9136 | sleep 3 if $tryno < $tries; # failure, try again | ||
457 | 9137 | } | ||
458 | 9138 | else { | ||
459 | 9139 | $have_mysql = 1; | ||
460 | 9140 | last TRY; # success | ||
461 | 9141 | } | ||
462 | 9142 | } | ||
463 | 9143 | if ( $have_mysql ) { | ||
464 | 9144 | eval { | ||
465 | 9145 | (undef, $uptime) = $cxn->dbh->selectrow_array("SHOW STATUS LIKE 'uptime'"); | ||
466 | 9146 | }; | ||
467 | 9147 | if ( $EVAL_ERROR ) { | ||
468 | 9148 | $logger->warning("Cannot check if MySQL restarted because " | ||
469 | 9149 | . "SHOW STATUS query failed: $EVAL_ERROR"); | ||
470 | 9150 | return; | ||
471 | 9151 | } | ||
472 | 9152 | } | ||
473 | 9153 | else { | ||
474 | 9154 | $logger->warning("Cannot check if MySQL restarted because " | ||
475 | 9155 | . "connection to MySQL failed: $e"); | ||
482 | 9156 | return; | 9177 | return; |
483 | 9157 | } | 9178 | } |
484 | 9158 | } | 9179 | } |
485 | @@ -9174,6 +9195,7 @@ | |||
486 | 9174 | . "elapsed=$elapsed_time expected=$exepected_uptime " | 9195 | . "elapsed=$elapsed_time expected=$exepected_uptime " |
487 | 9175 | . "+/- ${margin}s actual=$uptime"); | 9196 | . "+/- ${margin}s actual=$uptime"); |
488 | 9176 | $state->{mysql_restarted} = ts(time, 1); # 1=UTC | 9197 | $state->{mysql_restarted} = ts(time, 1); # 1=UTC |
489 | 9198 | $state->{need_mysql_version} = 1; | ||
490 | 9177 | } | 9199 | } |
491 | 9178 | } | 9200 | } |
492 | 9179 | 9201 | ||
493 | @@ -9259,14 +9281,13 @@ | |||
494 | 9259 | pt-agent is the client-side agent for Percona Cloud Tools. It is not | 9281 | pt-agent is the client-side agent for Percona Cloud Tools. It is not |
495 | 9260 | a general command line tool like other tools in Percona Toolkit, it is | 9282 | a general command line tool like other tools in Percona Toolkit, it is |
496 | 9261 | configured and controlled through the web at https://cloud.percona.com. | 9283 | configured and controlled through the web at https://cloud.percona.com. |
498 | 9262 | Please contact Percona or visit https://cloud.percona.com for more information. | 9284 | Visit https://cloud.percona.com for more information and to sign up. |
499 | 9263 | 9285 | ||
500 | 9264 | =head1 DESCRIPTION | 9286 | =head1 DESCRIPTION |
501 | 9265 | 9287 | ||
502 | 9266 | pt-agent is the client-side agent for Percona Cloud Tools (PCT). It is | 9288 | pt-agent is the client-side agent for Percona Cloud Tools (PCT). It is |
503 | 9267 | controlled and configured through the web app at https://cloud.percona.com. | 9289 | controlled and configured through the web app at https://cloud.percona.com. |
506 | 9268 | An account with Percona is required to use pt-agent. Please contact Percona | 9290 | Visit https://cloud.percona.com for more information and to sign up. |
505 | 9269 | or visit https://cloud.percona.com for more information. | ||
507 | 9270 | 9291 | ||
508 | 9271 | pt-agent, or "the agent", is a single, unique instance of the tool running | 9292 | pt-agent, or "the agent", is a single, unique instance of the tool running |
509 | 9272 | on a server. Two agents cannot run on the same server (see L<"--pid">). | 9293 | on a server. Two agents cannot run on the same server (see L<"--pid">). |
510 | @@ -9274,9 +9295,9 @@ | |||
511 | 9274 | The agent is a daemon that runs as root. It should be started with | 9295 | The agent is a daemon that runs as root. It should be started with |
512 | 9275 | L<"--daemonize">. It connects periodically to Percona to update | 9296 | L<"--daemonize">. It connects periodically to Percona to update |
513 | 9276 | its configuration and services, and it schedules L<"--run-service"> and | 9297 | its configuration and services, and it schedules L<"--run-service"> and |
517 | 9277 | L<"--send-data"> instances of itself. Other than L<"INSTALLING"> and starting | 9298 | L<"--send-data"> instances of itself using cron. Other than L<"INSTALLING"> |
518 | 9278 | the agent locally, all control and configuration is done through the web | 9299 | and starting the agent locally, all control and configuration is done through |
519 | 9279 | at https://cloud.percona.com. | 9300 | the web at https://cloud.percona.com. |
520 | 9280 | 9301 | ||
521 | 9281 | =head1 INSTALLING | 9302 | =head1 INSTALLING |
522 | 9282 | 9303 | ||
523 | @@ -9295,6 +9316,44 @@ | |||
524 | 9295 | 9316 | ||
525 | 9296 | Please contact Percona if you need help installing the agent. | 9317 | Please contact Percona if you need help installing the agent. |
526 | 9297 | 9318 | ||
527 | 9319 | =head2 SLAVE INSTALL | ||
528 | 9320 | |||
529 | 9321 | There are two ways to install pt-agent on a slave. The first and best way | ||
530 | 9322 | is to install the agent on the master so that the L<"MYSQL USER"> is created | ||
531 | 9323 | on the master and replicates to slaves. This is best because it avoids | ||
532 | 9324 | writing to the slave. Then create the C</etc/percona/agent/> directory on | ||
533 | 9325 | the slave and copy in to it C</etc/percona/agent/my.cnf> from the master. | ||
534 | 9326 | Run L<"--install"> on the slave and pt-agent will automatically detect and | ||
535 | 9327 | use the MySQL user and password in C</etc/percona/agent/my.cnf>. Repeat the | ||
536 | 9328 | process for other slaves. | ||
537 | 9329 | |||
538 | 9330 | The second way to install pt-agent on a slave is not safe because it writes | ||
539 | 9331 | directly to the slave: specify L<"--install-options"> | ||
540 | 9332 | C<force_dangerous_slave_install> in addition to L<"--install">. As the | ||
541 | 9333 | install option name implies, this is dangerous, but it forces pt-agent | ||
542 | 9334 | to ignore that MySQL is a slave. | ||
543 | 9335 | |||
544 | 9336 | =head2 Percona XtraDB Cluster (PXC) INSTALL | ||
545 | 9337 | |||
546 | 9338 | Installing pt-agent on Percona XtraDB Cluster (PXC) nodes is the same as | ||
547 | 9339 | installing it safely on slaves. First install the agent on any node. This | ||
548 | 9340 | will create the L<"MYSQL USER"> that will replicate to all other nodes. | ||
549 | 9341 | Then create the C</etc/percona/agent/> directory on another node and copy in | ||
550 | 9342 | to it C</etc/percona/agent/my.cnf> from the first node where pt-agent was | ||
551 | 9343 | installed. Run L<"--install"> on the node and pt-agent will automatically | ||
552 | 9344 | detect and use the MySQL user and password in C</etc/percona/agent/my.cnf>. | ||
553 | 9345 | Repeat the process for other nodes. | ||
554 | 9346 | |||
555 | 9347 | =head1 MYSQL USER | ||
556 | 9348 | |||
557 | 9349 | During L<"--install">, pt-agent creates the following MySQL user: | ||
558 | 9350 | |||
559 | 9351 | GRANT SUPER, USAGE ON *.* TO 'pt_agent'@'localhost' IDENTIFIED BY 'pass' | ||
560 | 9352 | |||
561 | 9353 | C<pass> is a random string. MySQL options for the agent are stored in | ||
562 | 9354 | C</etc/percona/agent/my.cnf>. The C<SUPER> privilege is required so that | ||
563 | 9355 | the agent can set global MySQL variables like C<long_query_time>. | ||
564 | 9356 | |||
565 | 9298 | =head1 EXIT STATUS | 9357 | =head1 EXIT STATUS |
566 | 9299 | 9358 | ||
567 | 9300 | pt-agent exists zero if no errors or warnings occurred, else it exits non-zero. | 9359 | pt-agent exists zero if no errors or warnings occurred, else it exits non-zero. |
568 | @@ -9637,7 +9696,7 @@ | |||
569 | 9637 | 9696 | ||
570 | 9638 | =over | 9697 | =over |
571 | 9639 | 9698 | ||
573 | 9640 | =item * An account with Percona | 9699 | =item * A Percona Cloud Tools account (https://cloud.percona.com) |
574 | 9641 | 9700 | ||
575 | 9642 | =item * Access to https://cloud-api.percona.com | 9701 | =item * Access to https://cloud-api.percona.com |
576 | 9643 | 9702 | ||
577 | @@ -9688,20 +9747,7 @@ | |||
578 | 9688 | =head1 DOWNLOADING | 9747 | =head1 DOWNLOADING |
579 | 9689 | 9748 | ||
580 | 9690 | Visit L<http://www.percona.com/software/percona-toolkit/> to download the | 9749 | Visit L<http://www.percona.com/software/percona-toolkit/> to download the |
595 | 9691 | latest release of Percona Toolkit. Or, get the latest release from the | 9750 | latest release of Percona Toolkit. |
582 | 9692 | command line: | ||
583 | 9693 | |||
584 | 9694 | wget percona.com/get/percona-toolkit.tar.gz | ||
585 | 9695 | |||
586 | 9696 | wget percona.com/get/percona-toolkit.rpm | ||
587 | 9697 | |||
588 | 9698 | wget percona.com/get/percona-toolkit.deb | ||
589 | 9699 | |||
590 | 9700 | You can also get individual tools from the latest release: | ||
591 | 9701 | |||
592 | 9702 | wget percona.com/get/TOOL | ||
593 | 9703 | |||
594 | 9704 | Replace C<TOOL> with the name of any tool. | ||
596 | 9705 | 9751 | ||
597 | 9706 | =head1 AUTHORS | 9752 | =head1 AUTHORS |
598 | 9707 | 9753 | ||
599 | 9708 | 9754 | ||
600 | === modified file 'lib/Cxn.pm' | |||
601 | --- lib/Cxn.pm 2013-06-17 06:23:11 +0000 | |||
602 | +++ lib/Cxn.pm 2013-12-11 04:33:49 +0000 | |||
603 | @@ -119,7 +119,7 @@ | |||
604 | 119 | 119 | ||
605 | 120 | sub connect { | 120 | sub connect { |
606 | 121 | my ( $self, %opts ) = @_; | 121 | my ( $self, %opts ) = @_; |
608 | 122 | my $dsn = $self->{dsn}; | 122 | my $dsn = $opts{dsn} || $self->{dsn}; |
609 | 123 | my $dp = $self->{DSNParser}; | 123 | my $dp = $self->{DSNParser}; |
610 | 124 | 124 | ||
611 | 125 | my $dbh = $self->{dbh}; | 125 | my $dbh = $self->{dbh}; |
612 | @@ -139,6 +139,13 @@ | |||
613 | 139 | } | 139 | } |
614 | 140 | 140 | ||
615 | 141 | $dbh = $self->set_dbh($dbh); | 141 | $dbh = $self->set_dbh($dbh); |
616 | 142 | if ( $opts{dsn} ) { | ||
617 | 143 | $self->{dsn} = $dsn; | ||
618 | 144 | $self->{dsn_name} = $dp->as_string($dsn, [qw(h P S)]) | ||
619 | 145 | || $dp->as_string($dsn, [qw(F)]) | ||
620 | 146 | || ''; | ||
621 | 147 | |||
622 | 148 | } | ||
623 | 142 | PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name}); | 149 | PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name}); |
624 | 143 | return $dbh; | 150 | return $dbh; |
625 | 144 | } | 151 | } |
626 | 145 | 152 | ||
627 | === modified file 'lib/Percona/Agent/Logger.pm' | |||
628 | --- lib/Percona/Agent/Logger.pm 2013-09-19 20:25:20 +0000 | |||
629 | +++ lib/Percona/Agent/Logger.pm 2013-12-11 04:33:49 +0000 | |||
630 | @@ -250,7 +250,7 @@ | |||
631 | 250 | sub debug { | 250 | sub debug { |
632 | 251 | my $self = shift; | 251 | my $self = shift; |
633 | 252 | return if $self->online_logging; | 252 | return if $self->online_logging; |
635 | 253 | return $self->_log(0, 'DEBUG', 1, @_); | 253 | return $self->_log(0, 'DEBUG', @_); |
636 | 254 | } | 254 | } |
637 | 255 | 255 | ||
638 | 256 | sub info { | 256 | sub info { |
639 | @@ -287,7 +287,7 @@ | |||
640 | 287 | } | 287 | } |
641 | 288 | 288 | ||
642 | 289 | sub _log { | 289 | sub _log { |
644 | 290 | my ($self, $online, $level, $msg, $offline) = @_; | 290 | my ($self, $online, $level, $msg) = @_; |
645 | 291 | 291 | ||
646 | 292 | my $ts = ts(time, 1); # 1=UTC | 292 | my $ts = ts(time, 1); # 1=UTC |
647 | 293 | my $level_number = level_number($level); | 293 | my $level_number = level_number($level); |
648 | 294 | 294 | ||
649 | === modified file 't/lib/Cxn.t' | |||
650 | --- t/lib/Cxn.t 2013-03-02 20:10:28 +0000 | |||
651 | +++ t/lib/Cxn.t 2013-12-11 04:33:49 +0000 | |||
652 | @@ -24,6 +24,8 @@ | |||
653 | 24 | my $dp = new DSNParser(opts=>$dsn_opts); | 24 | my $dp = new DSNParser(opts=>$dsn_opts); |
654 | 25 | my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); | 25 | my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); |
655 | 26 | my $master_dbh = $sb->get_dbh_for('master'); | 26 | my $master_dbh = $sb->get_dbh_for('master'); |
656 | 27 | my $slave1_dbh = $sb->get_dbh_for('slave1'); | ||
657 | 28 | my $slave1_dsn = $sb->dsn_for('slave1'); | ||
658 | 27 | 29 | ||
659 | 28 | if ( !$master_dbh ) { | 30 | if ( !$master_dbh ) { |
660 | 29 | plan skip_all => 'Cannot connect to sandbox master'; | 31 | plan skip_all => 'Cannot connect to sandbox master'; |
661 | @@ -320,6 +322,58 @@ | |||
662 | 320 | unlink $outfile if -f $outfile; | 322 | unlink $outfile if -f $outfile; |
663 | 321 | 323 | ||
664 | 322 | # ############################################################################# | 324 | # ############################################################################# |
665 | 325 | # Re-connect with new DSN. | ||
666 | 326 | # ############################################################################# | ||
667 | 327 | |||
668 | 328 | SKIP: { | ||
669 | 329 | skip "Cannot connect to slave1", 4 unless $slave1_dbh; | ||
670 | 330 | |||
671 | 331 | $cxn = make_cxn( | ||
672 | 332 | dsn_string => 'h=127.1,P=12345,u=msandbox,p=msandbox', | ||
673 | 333 | ); | ||
674 | 334 | |||
675 | 335 | $cxn->connect(); | ||
676 | 336 | ok( | ||
677 | 337 | $cxn->dbh()->ping(), | ||
678 | 338 | "First connect()" | ||
679 | 339 | ); | ||
680 | 340 | |||
681 | 341 | ($row) = $cxn->dbh()->selectrow_hashref('SHOW SLAVE STATUS'); | ||
682 | 342 | ok( | ||
683 | 343 | !defined $row, | ||
684 | 344 | "First connect() to master" | ||
685 | 345 | ) or diag(Dumper($row)); | ||
686 | 346 | |||
687 | 347 | $cxn->dbh->disconnect(); | ||
688 | 348 | $cxn->connect(dsn => $dp->parse($slave1_dsn)); | ||
689 | 349 | |||
690 | 350 | ok( | ||
691 | 351 | $cxn->dbh()->ping(), | ||
692 | 352 | "Re-connect connect()" | ||
693 | 353 | ); | ||
694 | 354 | |||
695 | 355 | ($row) = $cxn->dbh()->selectrow_hashref('SHOW SLAVE STATUS'); | ||
696 | 356 | ok( | ||
697 | 357 | $row, | ||
698 | 358 | "Re-connect connect(slave_dsn) to slave" | ||
699 | 359 | ) or diag(Dumper($row)); | ||
700 | 360 | |||
701 | 361 | $cxn->dbh->disconnect(); | ||
702 | 362 | $cxn->connect(); | ||
703 | 363 | |||
704 | 364 | ok( | ||
705 | 365 | $cxn->dbh()->ping(), | ||
706 | 366 | "Re-re-connect connect()" | ||
707 | 367 | ); | ||
708 | 368 | |||
709 | 369 | ($row) = $cxn->dbh()->selectrow_hashref('SHOW SLAVE STATUS'); | ||
710 | 370 | ok( | ||
711 | 371 | $row, | ||
712 | 372 | "Re-re-connect connect() to slave" | ||
713 | 373 | ) or diag(Dumper($row)); | ||
714 | 374 | } | ||
715 | 375 | |||
716 | 376 | # ############################################################################# | ||
717 | 323 | # Done. | 377 | # Done. |
718 | 324 | # ############################################################################# | 378 | # ############################################################################# |
719 | 325 | $master_dbh->disconnect() if $master_dbh; | 379 | $master_dbh->disconnect() if $master_dbh; |