2064
2098
# ###########################################################################
2066
2100
# ###########################################################################
2101
# VersionCheck package
2102
# This package is a copy without comments from the original. The original
2103
# with comments and its test file can be found in the Bazaar repository at,
2104
# lib/VersionCheck.pm
2105
# t/lib/VersionCheck.t
2106
# See https://launchpad.net/percona-toolkit for more information.
2107
# ###########################################################################
2109
package VersionCheck;
2112
use warnings FATAL => 'all';
2113
use English qw(-no_match_vars);
2115
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2117
use File::Basename ();
2118
use Data::Dumper ();
2121
local $Data::Dumper::Indent = 1;
2122
local $Data::Dumper::Sortkeys = 1;
2123
local $Data::Dumper::Quotekeys = 0;
2125
Data::Dumper::Dumper(@_);
2129
my ($class, %args) = @_;
2135
|perl_module_version
2140
return bless $self, $class;
2143
sub parse_server_response {
2144
my ($self, %args) = @_;
2145
my @required_args = qw(response);
2146
foreach my $arg ( @required_args ) {
2147
die "I need a $arg arugment" unless $args{$arg};
2149
my ($response) = @args{@required_args};
2152
my ($item, $type, $vars) = split(";", $_);
2153
if ( !defined $args{split_vars} || $args{split_vars} ) {
2154
$vars = [ split(",", ($vars || '')) ];
2161
} split("\n", $response);
2163
PTDEBUG && _d('Items:', Dumper(\%items));
2169
my ($self, %args) = @_;
2170
my @required_args = qw(items);
2171
foreach my $arg ( @required_args ) {
2172
die "I need a $arg arugment" unless $args{$arg};
2174
my ($items) = @args{@required_args};
2175
my $dbh = $args{dbh}; # optional
2178
foreach my $item ( values %$items ) {
2179
next unless $self->valid_item($item);
2182
my $func = 'get_' . $item->{type};
2183
my $version = $self->$func(
2189
$versions{$item->{item}} = $version;
2192
if ( $EVAL_ERROR ) {
2193
PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
2201
my ($self, $item) = @_;
2202
return unless $item;
2204
if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
2205
PTDEBUG && _d('Invalid type:', $item->{type});
2212
sub get_os_version {
2215
chomp(my $platform = `uname -s`);
2216
PTDEBUG && _d('platform:', $platform);
2217
return $OSNAME unless $platform;
2219
chomp(my $lsb_release
2220
= `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
2221
PTDEBUG && _d('lsb_release:', $lsb_release);
2225
if ( $platform eq 'Linux' ) {
2226
if ( -f "/etc/fedora-release" ) {
2227
$release = `cat /etc/fedora-release`;
2229
elsif ( -f "/etc/redhat-release" ) {
2230
$release = `cat /etc/redhat-release`;
2232
elsif ( -f "/etc/system-release" ) {
2233
$release = `cat /etc/system-release`;
2235
elsif ( $lsb_release ) {
2236
$release = `$lsb_release -ds`;
2238
elsif ( -f "/etc/lsb-release" ) {
2239
$release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
2240
$release =~ s/^\w+="([^"]+)".+/$1/;
2242
elsif ( -f "/etc/debian_version" ) {
2243
chomp(my $rel = `cat /etc/debian_version`);
2244
$release = "Debian $rel";
2245
if ( -f "/etc/apt/sources.list" ) {
2246
chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
2247
$release .= " ($code_name)" if $code_name;
2250
elsif ( `ls /etc/*release 2>/dev/null` ) {
2251
if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
2252
$release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
2255
$release = `cat /etc/*release | head -n1`;
2259
elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
2260
my $rel = `uname -r`;
2261
$release = "$platform $rel";
2263
elsif ( $platform eq "SunOS" ) {
2264
my $rel = `head -n1 /etc/release` || `uname -r`;
2265
$release = "$platform $rel";
2269
PTDEBUG && _d('Failed to get the release, using platform');
2270
$release = $platform;
2274
PTDEBUG && _d('OS version =', $release);
2278
sub get_perl_version {
2279
my ($self, %args) = @_;
2280
my $item = $args{item};
2281
return unless $item;
2283
my $version = sprintf '%vd', $PERL_VERSION;
2284
PTDEBUG && _d('Perl version', $version);
2288
sub get_perl_module_version {
2289
my ($self, %args) = @_;
2290
my $item = $args{item};
2291
return unless $item;
2293
my $var = $item->{item} . '::VERSION';
2294
my $version = _get_scalar($var);
2295
PTDEBUG && _d('Perl version for', $var, '=', "$version");
2297
return $version ? "$version" : $version;
2302
return ${*{shift()}};
2305
sub get_mysql_variable {
2307
return $self->_get_from_mysql(
2308
show => 'VARIABLES',
2314
sub _get_from_mysql {
2315
my ($self, %args) = @_;
2316
my $show = $args{show};
2317
my $item = $args{item};
2318
my $dbh = $args{dbh};
2319
return unless $show && $item && $dbh;
2321
local $dbh->{FetchHashKeyName} = 'NAME_lc';
2322
my $sql = qq/SHOW $show/;
2323
PTDEBUG && _d($sql);
2324
my $rows = $dbh->selectall_hashref($sql, 'variable_name');
2327
foreach my $var ( @{$item->{vars}} ) {
2329
my $version = $rows->{$var}->{value};
2330
PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
2331
push @versions, $version;
2334
return join(' ', @versions);
2337
sub get_bin_version {
2338
my ($self, %args) = @_;
2339
my $item = $args{item};
2340
my $cmd = $item->{item};
2343
my $sanitized_command = File::Basename::basename($cmd);
2344
PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
2345
return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
2347
my $output = `$sanitized_command --version 2>&1`;
2348
PTDEBUG && _d('output:', $output);
2350
my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
2352
PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
2357
my ($package, undef, $line) = caller 0;
2358
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2359
map { defined $_ ? $_ : 'undef' }
2361
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2366
# ###########################################################################
2367
# End VersionCheck package
2368
# ###########################################################################
2370
# ###########################################################################
2372
# This package is a copy without comments from the original. The original
2373
# with comments and its test file can be found in the Bazaar repository at,
2376
# See https://launchpad.net/percona-toolkit for more information.
2377
# ###########################################################################
2382
$HTTPMicro::VERSION = '0.001';
2392
@attributes = qw(agent timeout);
2394
for my $accessor ( @attributes ) {
2395
*{$accessor} = sub {
2396
@_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
2402
my($class, %args) = @_;
2403
(my $agent = $class) =~ s{::}{-}g;
2405
agent => $agent . "/" . ($class->VERSION || 0),
2408
for my $key ( @attributes ) {
2409
$self->{$key} = $args{$key} if exists $args{$key}
2411
return bless $self, $class;
2415
my ($self, $method, $url, $args) = @_;
2416
@_ == 3 || (@_ == 4 && ref $args eq 'HASH')
2417
or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
2418
$args ||= {}; # we keep some state in this during _request
2422
$response = eval { $self->_request($method, $url, $args) };
2423
last unless $@ && $method eq 'GET'
2424
&& $@ =~ m{^(?:Socket closed|Unexpected end)};
2431
reason => 'Internal Exception',
2434
'content-type' => 'text/plain',
2435
'content-length' => length $e,
2443
my ($self, $method, $url, $args) = @_;
2445
my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
2450
host_port => ($port == 80 ? $host : "$host:$port"),
2455
my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
2457
$handle->connect($scheme, $host, $port);
2459
$self->_prepare_headers_and_cb($request, $args);
2460
$handle->write_request_header(@{$request}{qw/method uri headers/});
2461
$handle->write_content_body($request) if $request->{content};
2464
do { $response = $handle->read_response_header }
2465
until (substr($response->{status},0,1) ne '1');
2467
if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
2468
$response->{content} = '';
2469
$handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
2473
$response->{success} = substr($response->{status},0,1) eq '2';
2477
sub _prepare_headers_and_cb {
2478
my ($self, $request, $args) = @_;
2480
for ($args->{headers}) {
2481
next unless defined;
2482
while (my ($k, $v) = each %$_) {
2483
$request->{headers}{lc $k} = $v;
2486
$request->{headers}{'host'} = $request->{host_port};
2487
$request->{headers}{'connection'} = "close";
2488
$request->{headers}{'user-agent'} ||= $self->{agent};
2490
if (defined $args->{content}) {
2491
$request->{headers}{'content-type'} ||= "application/octet-stream";
2492
utf8::downgrade($args->{content}, 1)
2493
or Carp::croak(q/Wide character in request message body/);
2494
$request->{headers}{'content-length'} = length $args->{content};
2495
$request->{content} = $args->{content};
2503
my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
2504
or Carp::croak(qq/Cannot parse URL: '$url'/);
2506
$scheme = lc $scheme;
2507
$path_query = "/$path_query" unless $path_query =~ m<\A/>;
2509
my $host = (length($authority)) ? lc $authority : 'localhost';
2510
$host =~ s/\A[^@]*@//; # userinfo
2512
$host =~ s/:([0-9]*)\z// && length $1
2514
: ($scheme eq 'http' ? 80 : undef);
2517
return ($scheme, $host, $port, $path_query);
2521
HTTPMicro::Handle; # hide from PAUSE/indexers
2526
use Errno qw[EINTR EPIPE];
2527
use IO::Socket qw[SOCK_STREAM];
2529
sub BUFSIZE () { 32768 }
2531
my $Printable = sub {
2536
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
2541
my ($class, %args) = @_;
2545
max_line_size => 16384,
2551
@_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
2552
my ($self, $scheme, $host, $port) = @_;
2554
if ( $scheme ne 'http' ) {
2555
croak(qq/Unsupported URL scheme '$scheme'/);
2558
$self->{fh} = 'IO::Socket::INET'->new(
2562
Type => SOCK_STREAM,
2563
Timeout => $self->{timeout}
2564
) or croak(qq/Could not connect to '$host:$port': $@/);
2566
binmode($self->{fh})
2567
or croak(qq/Could not binmode() socket: '$!'/);
2569
$self->{host} = $host;
2570
$self->{port} = $port;
2576
@_ == 1 || croak(q/Usage: $handle->close()/);
2578
CORE::close($self->{fh})
2579
or croak(qq/Could not close socket: '$!'/);
2583
@_ == 2 || croak(q/Usage: $handle->write(buf)/);
2584
my ($self, $buf) = @_;
2586
my $len = length $buf;
2589
local $SIG{PIPE} = 'IGNORE';
2593
or croak(q/Timed out while waiting for socket to become ready for writing/);
2594
my $r = syswrite($self->{fh}, $buf, $len, $off);
2598
last unless $len > 0;
2600
elsif ($! == EPIPE) {
2601
croak(qq/Socket closed by remote server: $!/);
2603
elsif ($! != EINTR) {
2604
croak(qq/Could not write to socket: '$!'/);
2611
@_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
2612
my ($self, $len) = @_;
2615
my $got = length $self->{rbuf};
2618
my $take = ($got < $len) ? $got : $len;
2619
$buf = substr($self->{rbuf}, 0, $take, '');
2625
or croak(q/Timed out while waiting for socket to become ready for reading/);
2626
my $r = sysread($self->{fh}, $buf, $len, length $buf);
2631
elsif ($! != EINTR) {
2632
croak(qq/Could not read from socket: '$!'/);
2636
croak(q/Unexpected end of stream/);
2642
@_ == 1 || croak(q/Usage: $handle->readline()/);
2646
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
2650
or croak(q/Timed out while waiting for socket to become ready for reading/);
2651
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
2655
elsif ($! != EINTR) {
2656
croak(qq/Could not read from socket: '$!'/);
2659
croak(q/Unexpected end of stream while looking for line/);
2662
sub read_header_lines {
2663
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
2664
my ($self, $headers) = @_;
2670
my $line = $self->readline;
2672
if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
2673
my ($field_name) = lc $1;
2674
$val = \($headers->{$field_name} = $2);
2676
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
2678
or croak(q/Unexpected header continuation line/);
2679
next unless length $1;
2680
$$val .= ' ' if length $$val;
2683
elsif ($line =~ /\A \x0D?\x0A \z/x) {
2687
croak(q/Malformed header line: / . $Printable->($line));
2693
sub write_header_lines {
2694
(@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
2695
my($self, $headers) = @_;
2698
while (my ($k, $v) = each %$headers) {
2699
my $field_name = lc $k;
2700
$field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
2701
or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
2702
$field_name =~ s/\b(\w)/\u$1/g;
2703
$buf .= "$field_name: $v\x0D\x0A";
2706
return $self->write($buf);
2709
sub read_content_body {
2710
@_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
2711
my ($self, $cb, $response, $len) = @_;
2712
$len ||= $response->{headers}{'content-length'};
2714
croak("No content-length in the returned response, and this "
2715
. "UA doesn't implement chunking") unless defined $len;
2718
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
2719
$cb->($self->read($read), $response);
2726
sub write_content_body {
2727
@_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
2728
my ($self, $request) = @_;
2729
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
2731
$len += $self->write($request->{content});
2733
$len == $content_length
2734
or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
2739
sub read_response_header {
2740
@_ == 1 || croak(q/Usage: $handle->read_response_header()/);
2743
my $line = $self->readline;
2745
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
2746
or croak(q/Malformed Status-Line: / . $Printable->($line));
2748
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
2753
headers => $self->read_header_lines,
2754
protocol => $protocol,
2758
sub write_request_header {
2759
@_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
2760
my ($self, $method, $request_uri, $headers) = @_;
2762
return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
2763
+ $self->write_header_lines($headers);
2767
my ($self, $type, $timeout) = @_;
2768
$timeout = $self->{timeout}
2769
unless defined $timeout && $timeout >= 0;
2771
my $fd = fileno $self->{fh};
2772
defined $fd && $fd >= 0
2773
or croak(q/select(2): 'Bad file descriptor'/);
2776
my $pending = $timeout;
2779
vec(my $fdset = '', $fd, 1) = 1;
2782
$nfound = ($type eq 'read')
2783
? select($fdset, undef, undef, $pending)
2784
: select(undef, $fdset, undef, $pending) ;
2785
if ($nfound == -1) {
2787
or croak(qq/select(2): '$!'/);
2788
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
2798
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
2800
return $self->_do_timeout('read', @_)
2804
@_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
2806
return $self->_do_timeout('write', @_)
2811
# ###########################################################################
2812
# End HTTPMicro package
2813
# ###########################################################################
2815
# ###########################################################################
2817
# This package is a copy without comments from the original. The original
2818
# with comments and its test file can be found in the Bazaar repository at,
2821
# See https://launchpad.net/percona-toolkit for more information.
2822
# ###########################################################################
2827
use warnings FATAL => 'all';
2828
use English qw(-no_match_vars);
2830
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2832
use File::Basename qw();
2833
use Data::Dumper qw();
2834
use Fcntl qw(:DEFAULT);
2838
my $dir = File::Spec->tmpdir();
2839
my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
2840
my $check_time_limit = 60 * 60 * 24; # one day
2843
local $Data::Dumper::Indent = 1;
2844
local $Data::Dumper::Sortkeys = 1;
2845
local $Data::Dumper::Quotekeys = 0;
2847
Data::Dumper::Dumper(@_);
2853
require VersionCheck;
2858
if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
2859
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2860
_d('--version-check is disabled by the PERCONA_VERSION_CHECK',
2861
'environment variable');
2866
if ( !time_to_check($check_time_file) ) {
2867
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2868
_d('It is not time to --version-checka again;',
2869
'only 1 check per', $check_time_limit, 'seconds, and the last',
2870
'check was performed on the modified time of', $check_time_file);
2875
my $dbh = shift; # optional
2876
my $advice = pingback(
2877
url => $ENV{PERCONA_VERSION_CHECK_URL} || 'http://v.percona.com',
2881
print "# Percona suggests these upgrades:\n";
2882
print join("\n", map { "# * $_" } @$advice);
2883
print "\n# Specify --no-version-check to disable these suggestions.\n\n";
2885
elsif ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2886
_d('--version-check worked, but there were no suggestions');
2889
if ( $EVAL_ERROR ) {
2890
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2891
_d('Error doing --version-check:', $EVAL_ERROR);
2900
my @required_args = qw(url);
2901
foreach my $arg ( @required_args ) {
2902
die "I need a $arg arugment" unless $args{$arg};
2904
my ($url) = @args{@required_args};
2906
my ($dbh, $ua, $vc) = @args{qw(dbh ua VersionCheck)};
2908
$ua ||= HTTPMicro->new( timeout => 2 );
2909
$vc ||= VersionCheck->new();
2911
my $response = $ua->request('GET', $url);
2912
PTDEBUG && _d('Server response:', Dumper($response));
2913
die "No response from GET $url"
2915
die "GET $url returned HTTP status $response->{status}; expected 200"
2916
if $response->{status} != 200;
2917
die "GET $url did not return any programs to check"
2918
if !$response->{content};
2920
my $items = $vc->parse_server_response(
2921
response => $response->{content}
2923
die "Failed to parse server requested programs: $response->{content}"
2924
if !scalar keys %$items;
2926
my $versions = $vc->get_versions(
2930
die "Failed to get any program versions; should have at least gotten Perl"
2931
if !scalar keys %$versions;
2933
my $client_content = encode_client_response(
2935
versions => $versions,
2938
my $client_response = {
2939
headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
2940
content => $client_content,
2942
PTDEBUG && _d('Client response:', Dumper($client_response));
2944
$response = $ua->request('POST', $url, $client_response);
2945
PTDEBUG && _d('Server suggestions:', Dumper($response));
2946
die "No response from POST $url $client_response"
2948
die "POST $url returned HTTP status $response->{status}; expected 200"
2949
if $response->{status} != 200;
2951
return unless $response->{content};
2953
$items = $vc->parse_server_response(
2954
response => $response->{content},
2957
die "Failed to parse server suggestions: $response->{content}"
2958
if !scalar keys %$items;
2959
my @suggestions = map { $_->{vars} }
2960
sort { $a->{item} cmp $b->{item} }
2963
return \@suggestions;
2968
die "I need a file argument" unless $file;
2971
PTDEBUG && _d('Creating', $file);
2976
my $mtime = (stat $file)[9];
2977
if ( !defined $mtime ) {
2978
PTDEBUG && _d('Error getting modified time of', $file);
2982
my $time = int(time());
2983
PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
2984
if ( ($time - $mtime) > $check_time_limit ) {
2994
sysopen my $fh, $file, O_WRONLY|O_CREAT|O_NONBLOCK
2995
or die "Cannot create $file : $!";
2996
close $fh or die "Cannot close $file : $!";
2997
utime(undef, undef, $file);
3000
sub encode_client_response {
3002
my @required_args = qw(items versions);
3003
foreach my $arg ( @required_args ) {
3004
die "I need a $arg arugment" unless $args{$arg};
3006
my ($items, $versions) = @args{@required_args};
3009
foreach my $item ( sort keys %$items ) {
3010
next unless exists $versions->{$item};
3011
push @lines, join(';', $item, $versions->{$item});
3014
my $client_response = join("\n", @lines) . "\n";
3015
PTDEBUG && _d('Client response:', $client_response);
3016
return $client_response;
3020
my ($package, undef, $line) = caller 0;
3021
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3022
map { defined $_ ? $_ : 'undef' }
3024
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3029
# ###########################################################################
3030
# End Pingback package
3031
# ###########################################################################
3033
# ###########################################################################
2067
3034
# This is a combination of modules and programs in one -- a runnable module.
2068
3035
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
2069
3036
# Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.