diff --git a/bin/pt-agent b/bin/pt-agent index 30eb900e..64dca4ca 100755 --- a/bin/pt-agent +++ b/bin/pt-agent @@ -14,6 +14,7 @@ use warnings FATAL => 'all'; BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit + Lmo::Utils Lmo::Meta Lmo::Object Lmo::Types @@ -103,7 +104,13 @@ sub _d { # ########################################################################### # Lmo::Utils package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t +# See https://launchpad.net/percona-toolkit for more information. # ########################################################################### +{ package Lmo::Utils; use strict; @@ -122,17 +129,11 @@ BEGIN { } { - # Gets the glob from a given string. no strict 'refs'; sub _glob_for { return \*{shift()} } - # Gets the stash from a given string. - # A stash is a symbol table hash; rough explanation on - # http://perldoc.perl.org/perlguts.html#Stashes-and-Globs - # But the gist of it is that we can use a hash-like thing to - # refer to a class and modify it. sub _stash_for { return \%{ shift() . "::" }; } @@ -156,6 +157,7 @@ sub _unimport_coderefs { } 1; +} # ########################################################################### # End Lmo::Utils package # ########################################################################### @@ -170,15 +172,14 @@ sub _unimport_coderefs { # ########################################################################### { package Lmo::Meta; - use strict; -use warnings FATAL => 'all'; +use warnings qw( FATAL all ); my %metadata_for; sub new { - shift; - return Lmo::Meta::Class->new(@_); + my $class = shift; + return bless { @_ }, $class } sub metadata_for { @@ -188,37 +189,28 @@ sub metadata_for { return $metadata_for{$class} ||= {}; } -{ - package Lmo::Meta::Class; +sub class { shift->{class} } - sub new { - my $class = shift; - return bless { @_ }, $class - } +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} - sub class { shift->{class} } +sub attributes_for_new { + my $self = shift; + my @attributes; - sub attributes { - my $self = shift; - return keys %{Lmo::Meta->metadata_for($self->class)} - } - - sub attributes_for_new { - my $self = shift; - my @attributes; - - my $class_metadata = Lmo::Meta->metadata_for($self->class); - while ( my ($attr, $meta) = each %$class_metadata ) { - if ( exists $meta->{init_arg} ) { - push @attributes, $meta->{init_arg} - if defined $meta->{init_arg}; - } - else { - push @attributes, $attr; - } + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; } - return @attributes; } + return @attributes; } 1; @@ -244,16 +236,8 @@ use warnings qw( FATAL all ); use Carp (); use Scalar::Util qw(blessed); -eval { - require Lmo::Meta; -}; - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } -} +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); sub new { my $class = shift; @@ -321,7 +305,7 @@ sub BUILDARGS { sub meta { my $class = shift; - $class = Scalar::Util::blessed($class) || $class; + $class = Scalar::Util::blessed($class) || $class; return Lmo::Meta->new(class => $class); } @@ -348,6 +332,7 @@ use warnings qw( FATAL all ); use Carp (); use Scalar::Util qw(looks_like_number blessed); + our %TYPES = ( Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, Num => sub { defined $_[0] && looks_like_number($_[0]) }, @@ -373,7 +358,7 @@ sub check_type_constaints { || Carp::confess( qq . qq - . (defined $val ? Percona::Toolkit::Dumper($val) : 'undef') ) + . (defined $val ? Lmo::Dumper($val) : 'undef') ) } sub _nested_constraints { @@ -440,56 +425,49 @@ sub _nested_constraints { # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +BEGIN { +$INC{"Lmo.pm"} = __FILE__; package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. -our $VERSION = '0.01'; use strict; use warnings qw( FATAL all ); use Carp (); -use Scalar::Util qw(blessed); +use Scalar::Util qw(looks_like_number blessed); -eval { - require Lmo::Meta; - require Lmo::Object; - require Lmo::Types; -}; +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} +use Lmo::Utils; my %export_for; sub import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my %exports = ( - extends => \&extends, - has => \&has, - ); + warnings->import(qw(FATAL all)); + strict->import(); - $export_for{$caller} = [ keys %exports ]; + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + override => \&override, + confess => \&Carp::confess, + ); - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } + $export_for{$caller} = \%exports; - if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { @_ = "Lmo::Object"; goto *{ _glob_for "${caller}::extends" }{CODE}; - } -}; + } +} sub extends { my $caller = scalar caller(); @@ -509,6 +487,25 @@ sub _load_module { return; } +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + sub has { my $names = shift; my $caller = scalar caller(); @@ -597,20 +594,20 @@ sub has { } } - *{ _glob_for "${caller}::$attribute" } = $method; + _install_coderef "${caller}::$attribute" => $method; if ( $args{required} ) { $class_metadata->{$attribute}{required} = 1; } if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } } if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } } if ($args{handles}) { @@ -643,7 +640,7 @@ sub _has_handles { map { $_, $_ } grep { $_ =~ $handles } grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -693,9 +690,18 @@ sub _set_inherited_metadata { sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); +} - delete $stash->{$_} for @{$export_for{$caller}}; +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) } BEGIN { @@ -728,6 +734,17 @@ BEGIN { } } +sub override { + my ($methods, $code) = @_; + my $caller = scalar caller; + + for my $method ( ref($methods) ? @$methods : $methods ) { + my $full_method = "${caller}::${method}"; + *{_glob_for $full_method} = $code; + } +} + +} 1; } # ########################################################################### @@ -1225,7 +1242,7 @@ package Percona::WebAPI::Resource::Agent; use Lmo; has 'uuid' => ( - is => 'r0', + is => 'ro', isa => 'Str', required => 0, ); @@ -1478,40 +1495,326 @@ sub resource_diff { { package VersionCheck; + use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; -use File::Basename (); -use Data::Dumper (); +use Data::Dumper; +local $Data::Dumper::Indent = 1; +local $Data::Dumper::Sortkeys = 1; +local $Data::Dumper::Quotekeys = 0; -sub Dumper { - local $Data::Dumper::Indent = 1; - local $Data::Dumper::Sortkeys = 1; - local $Data::Dumper::Quotekeys = 0; +use Digest::MD5 qw(md5_hex); +use Sys::Hostname qw(hostname); +use File::Basename qw(); +use File::Spec; +use FindBin qw(); - Data::Dumper::Dumper(@_); +eval { + require Percona::Toolkit; + require HTTPMicro; +}; + +{ + my $file = 'percona-version-check'; + my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; + my @vc_dirs = ( + '/etc/percona', + '/etc/percona-toolkit', + '/tmp', + "$home", + ); + + sub version_check_file { + foreach my $dir ( @vc_dirs ) { + if ( -d $dir && -w $dir ) { + PTDEBUG && _d('Version check file', $file, 'in', $dir); + return $dir . '/' . $file; + } + } + PTDEBUG && _d('Version check file', $file, 'in', $ENV{PWD}); + return $file; # in the CWD + } } -sub new { - my ($class, %args) = @_; - my $self = { - valid_types => qr/ - ^(?: - os_version - |perl_version - |perl_module_version - |mysql_variable - |bin_version - )$/x, +sub version_check_time_limit { + return 60 * 60 * 24; # one day +} + + +sub version_check { + my (%args) = @_; + + my $instances = $args{instances} || []; + my $instances_to_check; + + PTDEBUG && _d('FindBin::Bin:', $FindBin::Bin); + if ( !$args{force} ) { + if ( $FindBin::Bin + && (-d "$FindBin::Bin/../.bzr" || -d "$FindBin::Bin/../../.bzr") ) { + PTDEBUG && _d("$FindBin::Bin/../.bzr disables --version-check"); + return; + } + } + + eval { + foreach my $instance ( @$instances ) { + my ($name, $id) = get_instance_id($instance); + $instance->{name} = $name; + $instance->{id} = $id; + } + + push @$instances, { name => 'system', id => 0 }; + + $instances_to_check = get_instances_to_check( + instances => $instances, + vc_file => $args{vc_file}, # testing + now => $args{now}, # testing + ); + PTDEBUG && _d(scalar @$instances_to_check, 'instances to check'); + return unless @$instances_to_check; + + my $protocol = 'https'; # optimistic, but... + eval { require IO::Socket::SSL; }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d($EVAL_ERROR); + $protocol = 'http'; + } + PTDEBUG && _d('Using', $protocol); + + my $advice = pingback( + instances => $instances_to_check, + protocol => $protocol, + url => $args{url} # testing + || $ENV{PERCONA_VERSION_CHECK_URL} # testing + || "$protocol://v.percona.com", + ); + if ( $advice ) { + PTDEBUG && _d('Advice:', Dumper($advice)); + if ( scalar @$advice > 1) { + print "\n# " . scalar @$advice . " software updates are " + . "available:\n"; + } + else { + print "\n# A software update is available:\n"; + } + print join("\n", map { "# * $_" } @$advice), "\n\n"; + } }; - return bless $self, $class; + if ( $EVAL_ERROR ) { + PTDEBUG && _d('Version check failed:', $EVAL_ERROR); + } + + if ( @$instances_to_check ) { + eval { + update_check_times( + instances => $instances_to_check, + vc_file => $args{vc_file}, # testing + now => $args{now}, # testing + ); + }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d('Error updating version check file:', $EVAL_ERROR); + } + } + + if ( $ENV{PTDEBUG_VERSION_CHECK} ) { + warn "Exiting because the PTDEBUG_VERSION_CHECK " + . "environment variable is defined.\n"; + exit 255; + } + + return; +} + +sub get_instances_to_check { + my (%args) = @_; + + my $instances = $args{instances}; + my $now = $args{now} || int(time); + my $vc_file = $args{vc_file} || version_check_file(); + + if ( !-f $vc_file ) { + PTDEBUG && _d('Version check file', $vc_file, 'does not exist;', + 'version checking all instances'); + return $instances; + } + + open my $fh, '<', $vc_file or die "Cannot open $vc_file: $OS_ERROR"; + chomp(my $file_contents = do { local $/ = undef; <$fh> }); + PTDEBUG && _d('Version check file', $vc_file, 'contents:', $file_contents); + close $fh; + my %last_check_time_for = $file_contents =~ /^([^,]+),(.+)$/mg; + + my $check_time_limit = version_check_time_limit(); + my @instances_to_check; + foreach my $instance ( @$instances ) { + my $last_check_time = $last_check_time_for{ $instance->{id} }; + PTDEBUG && _d('Intsance', $instance->{id}, 'last checked', + $last_check_time, 'now', $now, 'diff', $now - ($last_check_time || 0), + 'hours until next check', + sprintf '%.2f', + ($check_time_limit - ($now - ($last_check_time || 0))) / 3600); + if ( !defined $last_check_time + || ($now - $last_check_time) >= $check_time_limit ) { + PTDEBUG && _d('Time to check', Dumper($instance)); + push @instances_to_check, $instance; + } + } + + return \@instances_to_check; +} + +sub update_check_times { + my (%args) = @_; + + my $instances = $args{instances}; + my $now = $args{now} || int(time); + my $vc_file = $args{vc_file} || version_check_file(); + PTDEBUG && _d('Updating last check time:', $now); + + open my $fh, '>', $vc_file or die "Cannot write to $vc_file: $OS_ERROR"; + foreach my $instance ( sort { $a->{id} cmp $b->{id} } @$instances ) { + PTDEBUG && _d('Updated:', Dumper($instance)); + print { $fh } $instance->{id} . ',' . $now . "\n"; + } + close $fh; + + return; +} + +sub get_instance_id { + my ($instance) = @_; + + my $dbh = $instance->{dbh}; + my $dsn = $instance->{dsn}; + + my $sql = q{SELECT CONCAT(@@hostname, @@port)}; + PTDEBUG && _d($sql); + my ($name) = eval { $dbh->selectrow_array($sql) }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d($EVAL_ERROR); + $sql = q{SELECT @@hostname}; + PTDEBUG && _d($sql); + ($name) = eval { $dbh->selectrow_array($sql) }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d($EVAL_ERROR); + $name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306); + } + else { + $sql = q{SHOW VARIABLES LIKE 'port'}; + PTDEBUG && _d($sql); + my (undef, $port) = eval { $dbh->selectrow_array($sql) }; + PTDEBUG && _d('port:', $port); + $name .= $port || ''; + } + } + my $id = md5_hex($name); + + PTDEBUG && _d('MySQL instance:', $id, $name, $dsn); + + return $name, $id; +} + + +sub pingback { + my (%args) = @_; + my @required_args = qw(url instances); + foreach my $arg ( @required_args ) { + die "I need a $arg arugment" unless $args{$arg}; + } + my $url = $args{url}; + my $instances = $args{instances}; + + my $ua = $args{ua} || HTTPMicro->new( timeout => 3 ); + + my $response = $ua->request('GET', $url); + PTDEBUG && _d('Server response:', Dumper($response)); + die "No response from GET $url" + if !$response; + die("GET on $url returned HTTP status $response->{status}; expected 200\n", + ($response->{content} || '')) if $response->{status} != 200; + die("GET on $url did not return any programs to check") + if !$response->{content}; + + my $items = parse_server_response( + response => $response->{content} + ); + die "Failed to parse server requested programs: $response->{content}" + if !scalar keys %$items; + + my $versions = get_versions( + items => $items, + instances => $instances, + ); + die "Failed to get any program versions; should have at least gotten Perl" + if !scalar keys %$versions; + + my $client_content = encode_client_response( + items => $items, + versions => $versions, + general_id => md5_hex( hostname() ), + ); + + my $client_response = { + headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) }, + content => $client_content, + }; + PTDEBUG && _d('Client response:', Dumper($client_response)); + + $response = $ua->request('POST', $url, $client_response); + PTDEBUG && _d('Server suggestions:', Dumper($response)); + die "No response from POST $url $client_response" + if !$response; + die "POST $url returned HTTP status $response->{status}; expected 200" + if $response->{status} != 200; + + return unless $response->{content}; + + $items = parse_server_response( + response => $response->{content}, + split_vars => 0, + ); + die "Failed to parse server suggestions: $response->{content}" + if !scalar keys %$items; + my @suggestions = map { $_->{vars} } + sort { $a->{item} cmp $b->{item} } + values %$items; + + return \@suggestions; +} + +sub encode_client_response { + my (%args) = @_; + my @required_args = qw(items versions general_id); + foreach my $arg ( @required_args ) { + die "I need a $arg arugment" unless $args{$arg}; + } + my ($items, $versions, $general_id) = @args{@required_args}; + + my @lines; + foreach my $item ( sort keys %$items ) { + next unless exists $versions->{$item}; + if ( ref($versions->{$item}) eq 'HASH' ) { + my $mysql_versions = $versions->{$item}; + for my $id ( sort keys %$mysql_versions ) { + push @lines, join(';', $id, $item, $mysql_versions->{$id}); + } + } + else { + push @lines, join(';', $general_id, $item, $versions->{$item}); + } + } + + my $client_response = join("\n", @lines) . "\n"; + return $client_response; } sub parse_server_response { - my ($self, %args) = @_; + my (%args) = @_; my @required_args = qw(response); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; @@ -1535,8 +1838,26 @@ sub parse_server_response { return \%items; } +my %sub_for_type = ( + os_version => \&get_os_version, + perl_version => \&get_perl_version, + perl_module_version => \&get_perl_module_version, + mysql_variable => \&get_mysql_variable, + bin_version => \&get_bin_version, +); + +sub valid_item { + my ($item) = @_; + return unless $item; + if ( !exists $sub_for_type{ $item->{type} } ) { + PTDEBUG && _d('Invalid type:', $item->{type}); + return 0; + } + return 1; +} + sub get_versions { - my ($self, %args) = @_; + my (%args) = @_; my @required_args = qw(items); foreach my $arg ( @required_args ) { die "I need a $arg arugment" unless $args{$arg}; @@ -1545,11 +1866,9 @@ sub get_versions { my %versions; foreach my $item ( values %$items ) { - next unless $self->valid_item($item); - + next unless valid_item($item); eval { - my $func = 'get_' . $item->{type}; - my $version = $self->$func( + my $version = $sub_for_type{ $item->{type} }->( item => $item, instances => $args{instances}, ); @@ -1566,21 +1885,8 @@ sub get_versions { return \%versions; } -sub valid_item { - my ($self, $item) = @_; - return unless $item; - - if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) { - PTDEBUG && _d('Invalid type:', $item->{type}); - return; - } - - return 1; -} sub get_os_version { - my ($self) = @_; - if ( $OSNAME eq 'MSWin32' ) { require Win32; return Win32::GetOSDisplayName(); @@ -1656,7 +1962,7 @@ sub get_os_version { } sub get_perl_version { - my ($self, %args) = @_; + my (%args) = @_; my $item = $args{item}; return unless $item; @@ -1666,47 +1972,40 @@ sub get_perl_version { } sub get_perl_module_version { - my ($self, %args) = @_; + my (%args) = @_; my $item = $args{item}; return unless $item; - - my $var = $item->{item} . '::VERSION'; - my $version = _get_scalar($var); - PTDEBUG && _d('Perl version for', $var, '=', "$version"); - return $version ? "$version" : $version; -} - -sub _get_scalar { - no strict; - return ${*{shift()}}; + my $var = '$' . $item->{item} . '::VERSION'; + my $version = eval "use $item->{item}; $var;"; + PTDEBUG && _d('Perl version for', $var, '=', $version); + return $version; } sub get_mysql_variable { - my $self = shift; - return $self->_get_from_mysql( + return get_from_mysql( show => 'VARIABLES', @_, ); } -sub _get_from_mysql { - my ($self, %args) = @_; +sub get_from_mysql { + my (%args) = @_; my $show = $args{show}; my $item = $args{item}; my $instances = $args{instances}; return unless $show && $item; if ( !$instances || !@$instances ) { - if ( $ENV{PTVCDEBUG} || PTDEBUG ) { - _d('Cannot check', $item, 'because there are no MySQL instances'); - } + PTDEBUG && _d('Cannot check', $item, + 'because there are no MySQL instances'); return; } my @versions; my %version_for; foreach my $instance ( @$instances ) { + next unless $instance->{id}; # special system instance has id=0 my $dbh = $instance->{dbh}; local $dbh->{FetchHashKeyName} = 'NAME_lc'; my $sql = qq/SHOW $show/; @@ -1721,7 +2020,6 @@ sub _get_from_mysql { 'on', $instance->{name}); push @versions, $version; } - $version_for{ $instance->{id} } = join(' ', @versions); } @@ -1729,7 +2027,7 @@ sub _get_from_mysql { } sub get_bin_version { - my ($self, %args) = @_; + my (%args) = @_; my $item = $args{item}; my $cmd = $item->{item}; return unless $cmd; @@ -2028,7 +2326,7 @@ sub get_dbh { if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) { $sql = qq{/*!40101 SET NAMES "$charset"*/}; - PTDEBUG && _d($dbh, ':', $sql); + PTDEBUG && _d($dbh, $sql); eval { $dbh->do($sql) }; if ( $EVAL_ERROR ) { die "Error setting NAMES to $charset: $EVAL_ERROR"; @@ -2043,13 +2341,8 @@ sub get_dbh { } } - if ( my $var = $self->prop('set-vars') ) { - $sql = "SET $var"; - PTDEBUG && _d($dbh, ':', $sql); - eval { $dbh->do($sql) }; - if ( $EVAL_ERROR ) { - die "Error setting $var: $EVAL_ERROR"; - } + if ( my $vars = $self->prop('set-vars') ) { + $self->set_vars($dbh, $vars); } $sql = 'SET @@SQL_QUOTE_SHOW_CREATE = 1' @@ -2124,6 +2417,57 @@ sub copy { return \%new_dsn; } +sub set_vars { + my ($self, $dbh, $vars) = @_; + + return unless $vars; + + foreach my $var ( sort keys %$vars ) { + my $val = $vars->{$var}->{val}; + + (my $quoted_var = $var) =~ s/_/\\_/; + my ($var_exists, $current_val); + eval { + ($var_exists, $current_val) = $dbh->selectrow_array( + "SHOW VARIABLES LIKE '$quoted_var'"); + }; + my $e = $EVAL_ERROR; + if ( $e ) { + PTDEBUG && _d($e); + } + + if ( $vars->{$var}->{default} && !$var_exists ) { + PTDEBUG && _d('Not setting default var', $var, + 'because it does not exist'); + next; + } + + if ( $current_val && $current_val eq $val ) { + PTDEBUG && _d('Not setting var', $var, 'because its value', + 'is already', $val); + next; + } + + my $sql = "SET SESSION $var=$val"; + PTDEBUG && _d($dbh, $sql); + eval { $dbh->do($sql) }; + if ( my $set_error = $EVAL_ERROR ) { + chomp($set_error); + $set_error =~ s/ at \S+ line \d+//; + my $msg = "Error setting $var: $set_error"; + if ( $current_val ) { + $msg .= " The current value for $var is $current_val. " + . "If the variable is read only (not dynamic), specify " + . "--set-vars $var=$current_val to avoid this warning, " + . "else manually set the variable and restart MySQL."; + } + warn $msg . "\n\n"; + } + } + + return; +} + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -2156,6 +2500,7 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; use List::Util qw(max); use Getopt::Long; +use Data::Dumper; my $POD_link_re = '[LC]<"?([^">]+)"?>'; @@ -3139,6 +3484,45 @@ sub _parse_synopsis { ); }; +sub set_vars { + my ($self, $file) = @_; + $file ||= $self->{file} || __FILE__; + + my %user_vars; + my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef; + if ( $user_vars ) { + foreach my $var_val ( @$user_vars ) { + my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; + die "Invalid --set-vars value: $var_val\n" unless $var && $val; + $user_vars{$var} = { + val => $val, + default => 0, + }; + } + } + + my %default_vars; + my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/); + if ( $default_vars ) { + %default_vars = map { + my $var_val = $_; + my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; + die "Invalid --set-vars value: $var_val\n" unless $var && $val; + $var => { + val => $val, + default => 1, + }; + } split("\n", $default_vars); + } + + my %vars = ( + %default_vars, # first the tool's defaults + %user_vars, # then the user's which overwrite the defaults + ); + PTDEBUG && _d('--set-vars:', Dumper(\%vars)); + return \%vars; +} + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -3204,39 +3588,50 @@ sub new { $dsn = $dp->copy($prev_dsn, $dsn); } + my $dsn_name = $dp->as_string($dsn, [qw(h P S)]) + || $dp->as_string($dsn, [qw(F)]) + || ''; + my $self = { - dsn => $dsn, - dbh => $args{dbh}, - dsn_name => $dp->as_string($dsn, [qw(h P S)]), - hostname => '', - set => $args{set}, - NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1, - dbh_set => 0, - OptionParser => $o, - DSNParser => $dp, + dsn => $dsn, + dbh => $args{dbh}, + dsn_name => $dsn_name, + hostname => '', + set => $args{set}, + NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1, + dbh_set => 0, + ask_pass => $o->get('ask-pass'), + DSNParser => $dp, is_cluster_node => undef, + parent => $args{parent}, }; return bless $self, $class; } sub connect { - my ( $self ) = @_; + my ( $self, %opts ) = @_; my $dsn = $self->{dsn}; my $dp = $self->{DSNParser}; - my $o = $self->{OptionParser}; my $dbh = $self->{dbh}; if ( !$dbh || !$dbh->ping() ) { - if ( $o->get('ask-pass') && !$self->{asked_for_pass} ) { + if ( $self->{ask_pass} && !$self->{asked_for_pass} ) { $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: "); $self->{asked_for_pass} = 1; } - $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 }); + $dbh = $dp->get_dbh( + $dp->get_cxn_params($dsn), + { + AutoCommit => 1, + %opts, + }, + ); } - PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name}); - return $self->set_dbh($dbh); + $dbh = $self->set_dbh($dbh); + PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name}); + return $dbh; } sub set_dbh { @@ -3259,6 +3654,11 @@ sub set_dbh { $self->{hostname} = $hostname; } + if ( $self->{parent} ) { + PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent'); + $dbh->{InactiveDestroy} = 1; + } + if ( my $set = $self->{set}) { $set->($dbh); } @@ -3268,6 +3668,13 @@ sub set_dbh { return $dbh; } +sub lost_connection { + my ($self, $e) = @_; + return 0 unless $e; + return $e =~ m/MySQL server has gone away/ + || $e =~ m/Lost connection to MySQL server/; +} + sub dbh { my ($self) = @_; return $self->{dbh}; @@ -3286,12 +3693,21 @@ sub name { sub DESTROY { my ($self) = @_; - if ( $self->{dbh} - && blessed($self->{dbh}) - && $self->{dbh}->can("disconnect") ) { - PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name}); + + PTDEBUG && _d('Destroying cxn'); + + if ( $self->{parent} ) { + PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent'); + } + elsif ( $self->{dbh} + && blessed($self->{dbh}) + && $self->{dbh}->can("disconnect") ) + { + PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname}, + $self->{dsn_name}); $self->{dbh}->disconnect(); } + return; } @@ -3325,6 +3741,11 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; +use Data::Dumper; +$Data::Dumper::Indent = 1; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Quotekeys = 0; + sub new { my ( $class, %args ) = @_; return bless {}, $class; @@ -3389,44 +3810,64 @@ sub join_quote { sub serialize_list { my ( $self, @args ) = @_; + PTDEBUG && _d('Serializing', Dumper(\@args)); return unless @args; - return $args[0] if @args == 1 && !defined $args[0]; + my @parts; + foreach my $arg ( @args ) { + if ( defined $arg ) { + $arg =~ s/,/\\,/g; # escape commas + $arg =~ s/\\N/\\\\N/g; # escape literal \N + push @parts, $arg; + } + else { + push @parts, '\N'; + } + } - die "Cannot serialize multiple values with undef/NULL" - if grep { !defined $_ } @args; - - return join ',', map { quotemeta } @args; + my $string = join(',', @parts); + PTDEBUG && _d('Serialized: <', $string, '>'); + return $string; } sub deserialize_list { my ( $self, $string ) = @_; - return $string unless defined $string; - my @escaped_parts = $string =~ / - \G # Start of string, or end of previous match. - ( # Each of these is an element in the original list. - [^\\,]* # Anything not a backslash or a comma - (?: # When we get here, we found one of the above. - \\. # A backslash followed by something so we can continue - [^\\,]* # Same as above. - )* # Repeat zero of more times. - ) - , # Comma dividing elements - /sxgc; + PTDEBUG && _d('Deserializing <', $string, '>'); + die "Cannot deserialize an undefined string" unless defined $string; - push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string; + my @parts; + foreach my $arg ( split(/(?new(%$service); - # Iterate through service's spool dir and send the data file therein. + # Send data files in the service's spool dir. # TODO: if the service dir doesn't exist? opendir(my $service_dh, $service_dir) or die "Error opening $service_dir: $OS_ERROR"; @@ -5110,7 +5551,7 @@ sub send_data { client => $client, agent => $agent, file => $file, - link => $service->links->{send_data}, + link => $service->links->{data}, json => $json, ); }; @@ -5164,19 +5605,25 @@ sub send_file { _info("Sending $file ($file_size bytes) to $link"); # Create a multi-part resource: first the Agent, so Percona knows - # from whom this data is coming, then the contents of the file as-is. - # We don't know or care about the file's contents, but Percona will. + # from whom this data is coming, then a special boundary value, + # then the contents of the file as-is. We don't know or care + # about the file's contents, but Percona will. my $agent_json = as_json($agent, json => $json); - my $data = slurp($file); - my $boundary = '--Ym91bmRhcnk='; # "boundary" in base64 + chomp($agent_json); + + my $boundary = '--Ym91bmRhcnk='; # "boundary" in base64 + + my $data = slurp($file); + $data =~ s/^\s+//; + $data =~ s/\s+$//; + + # Put it all together: my $resource = <post( link => $link, resources => $resource, diff --git a/bin/pt-query-digest b/bin/pt-query-digest index 6095a14b..690a494a 100755 --- a/bin/pt-query-digest +++ b/bin/pt-query-digest @@ -7627,8 +7627,8 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; my $have_json = eval { require JSON }; -our $pretty_json = 0; -our $sorted_json = 0; +our $pretty_json = $ENV{PTTEST_PRETTY_JSON} ? 1 : 0; +our $sorted_json = $ENV{PTTEST_PRETTY_JSON} ? 1 : 0; extends qw(QueryReportFormatter); diff --git a/lib/Percona/WebAPI/Resource/Agent.pm b/lib/Percona/WebAPI/Resource/Agent.pm index 5378a445..760fb901 100644 --- a/lib/Percona/WebAPI/Resource/Agent.pm +++ b/lib/Percona/WebAPI/Resource/Agent.pm @@ -23,7 +23,7 @@ package Percona::WebAPI::Resource::Agent; use Lmo; has 'uuid' => ( - is => 'r0', + is => 'ro', isa => 'Str', required => 0, ); diff --git a/t/pt-agent/make_new_crontab.t b/t/pt-agent/make_new_crontab.t index d235c0b2..c086f4e7 100644 --- a/t/pt-agent/make_new_crontab.t +++ b/t/pt-agent/make_new_crontab.t @@ -55,7 +55,7 @@ my $run0 = Percona::WebAPI::Resource::Task->new( ); my $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '* 8 * * 1,2,3,4,5', spool_schedule => '* 9 * * 1,2,3,4,5', tasks => [ $run0 ], @@ -127,8 +127,8 @@ SKIP: { is( $new_crontab, "* 0 * * * date > /dev/null -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history ", "Runs crontab -l by default" ); diff --git a/t/pt-agent/run_agent.t b/t/pt-agent/run_agent.t index 300ebbf6..efde0c2b 100644 --- a/t/pt-agent/run_agent.t +++ b/t/pt-agent/run_agent.t @@ -106,12 +106,13 @@ my $run0 = Percona::WebAPI::Resource::Task->new( ); my $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '1 * * * *', spool_schedule => '2 * * * *', tasks => [ $run0 ], links => { - send_data => '/query-monitor', + self => '/query-history', + data => '/query-history/data', }, ); @@ -189,35 +190,35 @@ is( ); ok( - -f "$tmpdir/services/query-monitor", - "Created services/query-monitor" + -f "$tmpdir/services/query-history", + "Created services/query-history" ) or diag($output); chomp(my $n_files = `ls -1 $tmpdir/services| wc -l | awk '{print \$1}'`); is( $n_files, 1, - "... only created services/query-monitor" + "... only created services/query-history" ); ok( no_diff( - "cat $tmpdir/services/query-monitor", + "cat $tmpdir/services/query-history", "t/pt-agent/samples/service001", ), - "query-monitor service file" + "query-history service file" ); $crontab = `crontab -l 2>/dev/null`; like( $crontab, - qr/pt-agent --run-service query-monitor$/m, + qr/pt-agent --run-service query-history$/m, "Scheduled --run-service with crontab" ); like( $crontab, - qr/pt-agent --send-data query-monitor$/m, + qr/pt-agent --send-data query-history$/m, "Scheduled --send-data with crontab" ); @@ -257,14 +258,14 @@ $ua->{responses}->{get} = [ @oktorun = (1, 1, 1, 0); # Between the 2nd and 3rd checks, remove the config file (~/.pt-agent.conf) -# and query-monitor service file. When the tool re-GETs these, they'll be +# and 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[2] = sub { unlink "$config_file"; - unlink "$tmpdir/services/query-monitor"; + unlink "$tmpdir/services/query-history"; Percona::Test::wait_until(sub { ! -f "$config_file" }); - Percona::Test::wait_until(sub { ! -f "$tmpdir/services/query-monitor" }); + Percona::Test::wait_until(sub { ! -f "$tmpdir/services/query-history" }); }; @wait = (); @@ -296,7 +297,7 @@ ok( ); ok( - ! -f "$tmpdir/services/query-monitor", + ! -f "$tmpdir/services/query-history", "No Service diff, no service file changes" ); diff --git a/t/pt-agent/run_service.t b/t/pt-agent/run_service.t index 9d8ba33b..4cbc44e9 100644 --- a/t/pt-agent/run_service.t +++ b/t/pt-agent/run_service.t @@ -13,6 +13,8 @@ use Test::More; use JSON; use File::Temp qw(tempdir); +$ENV{PTTEST_PRETTY_JSON} = 1; + use Percona::Test; use Percona::Test::Mock::UserAgent; require "$trunk/bin/pt-agent"; @@ -57,12 +59,12 @@ my $run0 = Percona::WebAPI::Resource::Task->new( name => 'query-history', number => '0', program => "$trunk/bin/pt-query-digest", - options => "--report-format profile $trunk/t/lib/samples/slowlogs/slow008.txt", + options => "--output json $trunk/t/lib/samples/slowlogs/slow008.txt", output => 'spool', ); my $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '1 * * * *', spool_schedule => '2 * * * *', tasks => [ $run0 ], @@ -76,7 +78,7 @@ my $exit_status; my $output = output( sub { $exit_status = pt_agent::run_service( - service => 'query-monitor', + service => 'query-history', spool_dir => $spool_dir, lib_dir => $tmpdir, ); @@ -86,17 +88,17 @@ my $output = output( ok( no_diff( - "cat $tmpdir/spool/query-monitor", - "$sample/spool001.txt", + "cat $tmpdir/spool/query-history", + "$sample/query-history/data001.json", ), - "1 run: spool data (spool001.txt)" + "1 run: spool data (query-history/data001.json)" ); chomp(my $n_files = `ls -1 $spool_dir | wc -l | awk '{print \$1}'`); is( $n_files, 1, - "1 run: only wrote spool data (spool001.txt)" + "1 run: only wrote spool data (query-history/data001.json)" ) or diag(`ls -l $spool_dir`); is( @@ -128,12 +130,12 @@ my $run1 = Percona::WebAPI::Resource::Task->new( name => 'query-history', number => '1', program => "$trunk/bin/pt-query-digest", - options => "--report-format profile __RUN_0_OUTPUT__", + options => "--output json __RUN_0_OUTPUT__", output => 'spool', ); $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '3 * * * *', spool_schedule => '4 * * * *', tasks => [ $run0, $run1 ], @@ -146,7 +148,7 @@ write_svc_files( $output = output( sub { $exit_status = pt_agent::run_service( - service => 'query-monitor', + service => 'query-history', spool_dir => $spool_dir, lib_dir => $tmpdir, ); @@ -156,8 +158,8 @@ $output = output( ok( no_diff( - "cat $tmpdir/spool/query-monitor", - "$sample/spool001.txt", + "cat $tmpdir/spool/query-history", + "$sample/query-history/data001.json", ), "2 runs: spool data" ); diff --git a/t/pt-agent/samples/crontab001.out b/t/pt-agent/samples/crontab001.out index 1cb21262..23624ee5 100644 --- a/t/pt-agent/samples/crontab001.out +++ b/t/pt-agent/samples/crontab001.out @@ -1,2 +1,2 @@ -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history diff --git a/t/pt-agent/samples/crontab002.out b/t/pt-agent/samples/crontab002.out index 3da1db8a..63137a3c 100644 --- a/t/pt-agent/samples/crontab002.out +++ b/t/pt-agent/samples/crontab002.out @@ -1,3 +1,3 @@ 17 3 * * 1 cmd -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history diff --git a/t/pt-agent/samples/crontab003.out b/t/pt-agent/samples/crontab003.out index 3da1db8a..63137a3c 100644 --- a/t/pt-agent/samples/crontab003.out +++ b/t/pt-agent/samples/crontab003.out @@ -1,3 +1,3 @@ 17 3 * * 1 cmd -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history diff --git a/t/pt-agent/samples/crontab004.out b/t/pt-agent/samples/crontab004.out index 1cb21262..23624ee5 100644 --- a/t/pt-agent/samples/crontab004.out +++ b/t/pt-agent/samples/crontab004.out @@ -1,2 +1,2 @@ -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history diff --git a/t/pt-agent/samples/query-monitor/data001 b/t/pt-agent/samples/query-history/data001 similarity index 100% rename from t/pt-agent/samples/query-monitor/data001 rename to t/pt-agent/samples/query-history/data001 diff --git a/t/pt-agent/samples/query-history/data001.json b/t/pt-agent/samples/query-history/data001.json new file mode 100644 index 00000000..3fe3cb96 --- /dev/null +++ b/t/pt-agent/samples/query-history/data001.json @@ -0,0 +1,73 @@ + +[ + { + "attributes" : { + "Lock_time" : { + "avg" : "0.009453", + "cnt" : "1.000000", + "max" : "0.009453", + "median" : "0.009453", + "min" : "0.009453", + "pct" : "0.33", + "pct_95" : "0.009453", + "stddev" : 0, + "sum" : "0.009453" + }, + "Query_time" : { + "avg" : "0.018799", + "cnt" : "1.000000", + "max" : "0.018799", + "median" : "0.018799", + "min" : "0.018799", + "pct" : "0.33", + "pct_95" : "0.018799", + "stddev" : 0, + "sum" : "0.018799" + }, + "Rows_examined" : { + "avg" : 0, + "cnt" : "1.000000", + "max" : "0", + "median" : 0, + "min" : "0", + "pct" : "0.33", + "pct_95" : 0, + "stddev" : 0, + "sum" : 0 + }, + "Rows_sent" : { + "avg" : 0, + "cnt" : "1.000000", + "max" : "0", + "median" : 0, + "min" : "0", + "pct" : "0.33", + "pct_95" : 0, + "stddev" : 0, + "sum" : 0 + }, + "bytes" : { + "value" : 31 + }, + "db" : { + "value" : "db2" + }, + "host" : { + "value" : "" + }, + "pos_in_log" : { + "value" : 435 + }, + "user" : { + "value" : "meow" + } + }, + "class" : { + "checksum" : "C72BF45D68E35A6E", + "cnt" : 1, + "fingerprint" : "select min(id),max(id) from tbl", + "sample" : "SELECT MIN(id),MAX(id) FROM tbl" + } + } +] + diff --git a/t/pt-agent/samples/query-history/data001.send b/t/pt-agent/samples/query-history/data001.send new file mode 100644 index 00000000..07bbdb33 --- /dev/null +++ b/t/pt-agent/samples/query-history/data001.send @@ -0,0 +1,76 @@ +{ + "hostname" : "prod1", + "uuid" : "123" +} +--Ym91bmRhcnk= +[ + { + "attributes" : { + "Lock_time" : { + "avg" : "0.009453", + "cnt" : "1.000000", + "max" : "0.009453", + "median" : "0.009453", + "min" : "0.009453", + "pct" : "0.33", + "pct_95" : "0.009453", + "stddev" : 0, + "sum" : "0.009453" + }, + "Query_time" : { + "avg" : "0.018799", + "cnt" : "1.000000", + "max" : "0.018799", + "median" : "0.018799", + "min" : "0.018799", + "pct" : "0.33", + "pct_95" : "0.018799", + "stddev" : 0, + "sum" : "0.018799" + }, + "Rows_examined" : { + "avg" : 0, + "cnt" : "1.000000", + "max" : "0", + "median" : 0, + "min" : "0", + "pct" : "0.33", + "pct_95" : 0, + "stddev" : 0, + "sum" : 0 + }, + "Rows_sent" : { + "avg" : 0, + "cnt" : "1.000000", + "max" : "0", + "median" : 0, + "min" : "0", + "pct" : "0.33", + "pct_95" : 0, + "stddev" : 0, + "sum" : 0 + }, + "bytes" : { + "value" : 31 + }, + "db" : { + "value" : "db2" + }, + "host" : { + "value" : "" + }, + "pos_in_log" : { + "value" : 435 + }, + "user" : { + "value" : "meow" + } + }, + "class" : { + "checksum" : "C72BF45D68E35A6E", + "cnt" : 1, + "fingerprint" : "select min(id),max(id) from tbl", + "sample" : "SELECT MIN(id),MAX(id) FROM tbl" + } + } +] diff --git a/t/pt-agent/samples/query-monitor/data001.send b/t/pt-agent/samples/query-monitor/data001.send deleted file mode 100644 index 068ed827..00000000 --- a/t/pt-agent/samples/query-monitor/data001.send +++ /dev/null @@ -1,13 +0,0 @@ -{ - "hostname" : "prod1", - "id" : "123" -} - ---Ym91bmRhcnk= -[ - { - query_id: 1, - arg: "select * from t where id = 1", - Query_time: 0.123456, - } -] diff --git a/t/pt-agent/samples/service001 b/t/pt-agent/samples/service001 index a7e5e76f..e30bee4e 100644 --- a/t/pt-agent/samples/service001 +++ b/t/pt-agent/samples/service001 @@ -1,8 +1,9 @@ { "links" : { - "send_data" : "/query-monitor" + "data" : "/query-history/data", + "self" : "/query-history" }, - "name" : "query-monitor", + "name" : "query-history", "run_schedule" : "1 * * * *", "spool_schedule" : "2 * * * *", "tasks" : [ diff --git a/t/pt-agent/samples/spool001.txt b/t/pt-agent/samples/spool001.txt deleted file mode 100644 index 0848ec00..00000000 --- a/t/pt-agent/samples/spool001.txt +++ /dev/null @@ -1,6 +0,0 @@ - -# Profile -# Rank Query ID Response time Calls R/Call V/M Item -# ==== ================== ============= ===== ====== ===== ========== -# 1 0xC72BF45D68E35A6E 0.0188 95.4% 1 0.0188 0.00 SELECT tbl -# MISC 0xMISC 0.0009 4.6% 2 0.0005 0.0 <2 ITEMS> diff --git a/t/pt-agent/samples/write_services001 b/t/pt-agent/samples/write_services001 index f285b1cb..c6b07207 100644 --- a/t/pt-agent/samples/write_services001 +++ b/t/pt-agent/samples/write_services001 @@ -1,8 +1,9 @@ { "links" : { - "send_data" : "/query-monitor" + "data" : "/query-history/data", + "self" : "/query-history" }, - "name" : "query-monitor", + "name" : "query-history", "run_schedule" : "1 * * * *", "spool_schedule" : "2 * * * *", "tasks" : [ diff --git a/t/pt-agent/schedule_services.t b/t/pt-agent/schedule_services.t index 28ae3e28..ea57c716 100644 --- a/t/pt-agent/schedule_services.t +++ b/t/pt-agent/schedule_services.t @@ -39,7 +39,7 @@ my $run0 = Percona::WebAPI::Resource::Task->new( ); my $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '* 8 * * 1,2,3,4,5', spool_schedule => '* 9 * * 1,2,3,4,5', tasks => [ $run0 ], @@ -84,8 +84,8 @@ $crontab = `crontab -l 2>/dev/null`; is( $crontab, "* 0 * * * date > /dev/null -* 8 * * 1,2,3,4,5 pt-agent --run-service query-monitor -* 9 * * 1,2,3,4,5 pt-agent --send-data query-monitor +* 8 * * 1,2,3,4,5 pt-agent --run-service query-history +* 9 * * 1,2,3,4,5 pt-agent --send-data query-history ", "schedule_services()" ); @@ -120,7 +120,7 @@ is( # ############################################################################# $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '* * * * Foo', # "foo":0: bad day-of-week spool_schedule => '* 8 * * Mon', tasks => [ $run0 ], diff --git a/t/pt-agent/send_data.t b/t/pt-agent/send_data.t index 83799501..d651f2db 100644 --- a/t/pt-agent/send_data.t +++ b/t/pt-agent/send_data.t @@ -42,7 +42,7 @@ my $links = { agents => '/agents', config => '/agents/1/config', services => '/agents/1/services', - 'query-monitor' => '/query-monitor', + 'query-history' => '/query-history', }; $ua->{responses}->{get} = [ @@ -64,14 +64,14 @@ is( ) or die; my $agent = Percona::WebAPI::Resource::Agent->new( - id => '123', + uuid => '123', hostname => 'prod1', ); is_deeply( as_hashref($agent), { - id => '123', + uuid => '123', hostname => 'prod1', }, 'Create mock Agent' @@ -82,13 +82,13 @@ is_deeply( # ############################################################################# my $tmpdir = tempdir("/tmp/pt-agent.$PID.XXXXXX", CLEANUP => 1); -mkdir "$tmpdir/query-monitor" - or die "Cannot mkdir $tmpdir/query-monitor: $OS_ERROR"; +mkdir "$tmpdir/query-history" + or die "Cannot mkdir $tmpdir/query-history: $OS_ERROR"; mkdir "$tmpdir/services" or die "Cannot mkdir $tmpdir/services: $OS_ERROR"; -`cp $trunk/$sample/query-monitor/data001 $tmpdir/query-monitor/`; -`cp $trunk/$sample/service001 $tmpdir/services/query-monitor`; +`cp $trunk/$sample/query-history/data001.json $tmpdir/query-history/`; +`cp $trunk/$sample/service001 $tmpdir/services/query-history`; $ua->{responses}->{post} = [ { @@ -101,7 +101,7 @@ my $output = output( pt_agent::send_data( client => $client, agent => $agent, - service => 'query-monitor', + service => 'query-history', lib_dir => $tmpdir, spool_dir => $tmpdir, json => $json, # optional, for testing @@ -119,22 +119,22 @@ is( is_deeply( $ua->{requests}, [ - 'POST /query-monitor', + 'POST /query-history/data', ], - "POST to Service.links.send_data" + "POST to Service.links.data" ); ok( no_diff( $client->ua->{content}->{post}->[0] || '', - "$sample/query-monitor/data001.send", + "$sample/query-history/data001.send", cmd_output => 1, ), - "Sent data file as multi-part resource (query-monitor/data001)" + "Sent data file as multi-part resource (query-history/data001)" ) or diag(Dumper($client->ua->{content}->{post})); ok( - !-f "$tmpdir/query-monitor/data001", + !-f "$tmpdir/query-history/data001.json", "Removed data file after sending successfully" ); diff --git a/t/pt-agent/write_services.t b/t/pt-agent/write_services.t index 2a302690..3a17736c 100644 --- a/t/pt-agent/write_services.t +++ b/t/pt-agent/write_services.t @@ -72,11 +72,14 @@ my $run0 = Percona::WebAPI::Resource::Task->new( ); my $svc0 = Percona::WebAPI::Resource::Service->new( - name => 'query-monitor', + name => 'query-history', run_schedule => '1 * * * *', spool_schedule => '2 * * * *', tasks => [ $run0 ], - links => { send_data => '/query-monitor' }, + links => { + self => '/query-history', + data => '/query-history/data', + }, ); # Key thing here is that the links are written because