| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package ElasticSearch::TestServer; | 
| 2 |  |  |  |  |  |  | $ElasticSearch::TestServer::VERSION = '0.68'; | 
| 3 | 1 |  |  | 1 |  | 1347 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 4 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 35 |  | 
| 5 | 1 |  |  | 1 |  | 6 | use ElasticSearch(); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 17 |  | 
| 6 | 1 |  |  | 1 |  | 987 | use POSIX 'setsid'; | 
|  | 1 |  |  |  |  | 15319 |  | 
|  | 1 |  |  |  |  | 9 |  | 
| 7 | 1 |  |  | 1 |  | 4371 | use IO::Socket(); | 
|  | 1 |  |  |  |  | 23511 |  | 
|  | 1 |  |  |  |  | 29 |  | 
| 8 | 1 |  |  | 1 |  | 1300 | use File::Temp 0.22 (); | 
|  | 1 |  |  |  |  | 14808 |  | 
|  | 1 |  |  |  |  | 38 |  | 
| 9 | 1 |  |  | 1 |  | 943 | use File::Spec::Functions qw(catfile); | 
|  | 1 |  |  |  |  | 1407 |  | 
|  | 1 |  |  |  |  | 86 |  | 
| 10 | 1 |  |  | 1 |  | 941 | use YAML qw(DumpFile); | 
|  | 1 |  |  |  |  | 10079 |  | 
|  | 1 |  |  |  |  | 81 |  | 
| 11 | 1 |  |  | 1 |  | 13 | use File::Path qw(rmtree); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 1 |  |  | 1 |  | 7 | use parent 'ElasticSearch'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 12 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | =head1 NAME | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | ElasticSearch::TestServer - Start an ElasticSearch cluster for testing | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | use ElasticSearch::TestServer; | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | $ENV{ES_HOME} = '/path/to/elasticsearch'; | 
| 24 |  |  |  |  |  |  | $ENV{ES_TRANSPORT} = 'http'; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | my $es = ElasticSearch::TestServer->new( | 
| 27 |  |  |  |  |  |  | home        => '/path/to/elasticsearch', | 
| 28 |  |  |  |  |  |  | instances   => 3, | 
| 29 |  |  |  |  |  |  | transport   => 'http', | 
| 30 |  |  |  |  |  |  | ip          => '127.0.0.1', | 
| 31 |  |  |  |  |  |  | trace_calls => 'logfile', | 
| 32 |  |  |  |  |  |  | port        => '9200', | 
| 33 |  |  |  |  |  |  | config      => { values to override} | 
| 34 |  |  |  |  |  |  | ); | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | ElasticSearch::TestServer is a utility module which will start an | 
| 39 |  |  |  |  |  |  | ElasticSearch cluster intended for testing, and shut the cluster | 
| 40 |  |  |  |  |  |  | down at the end, even if your code exits abnormally. | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | By default, it uses C transport, the C gateway, and | 
| 43 |  |  |  |  |  |  | starts 3 instances on C, starting with C 9200 if | 
| 44 |  |  |  |  |  |  | the C is C, C, C, C, C | 
| 45 |  |  |  |  |  |  | or 9500 if C. | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | It is a subclass of L, so C<< ElasticSearch::TestServer->new >> | 
| 48 |  |  |  |  |  |  | returns an ElasticSearch instance. | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =cut | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | #=================================== | 
| 53 |  |  |  |  |  |  | sub new { | 
| 54 |  |  |  |  |  |  | #=================================== | 
| 55 | 1 |  |  | 1 | 1 | 507 | my $class  = shift; | 
| 56 | 0 |  |  |  |  | 0 | my %params = ( | 
| 57 |  |  |  |  |  |  | home      => $ENV{ES_HOME}, | 
| 58 |  |  |  |  |  |  | transport => $ENV{ES_TRANSPORT} || 'http', | 
| 59 |  |  |  |  |  |  | instances => 3, | 
| 60 |  |  |  |  |  |  | ip        => '127.0.0.1', | 
| 61 | 1 | 50 | 50 |  |  | 19 | ref $_[0] eq 'HASH' ? %{ shift() } : @_ | 
| 62 |  |  |  |  |  |  | ); | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 1 | 50 |  |  |  | 14 | my $home = delete $params{home} or die < | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | ************************************************************ | 
| 67 |  |  |  |  |  |  | ElasticSearch home directory not specified | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | Please either set \$ENV{ES_HOME} or pass a value | 
| 70 |  |  |  |  |  |  | for 'home' to new() | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | ************************************************************ | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | NO_HOME | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 0 |  |  |  |  |  | my $transport = $params{transport}; | 
| 77 | 0 |  | 0 |  |  |  | my $port      = delete $params{port} | 
| 78 |  |  |  |  |  |  | || ( $transport eq 'thrift' ? 9500 : 9200 ); | 
| 79 | 0 |  |  |  |  |  | my $instances = delete $params{instances}; | 
| 80 | 0 | 0 |  |  |  |  | my $plugin    = $ElasticSearch::Transport::Transport{$transport} | 
| 81 |  |  |  |  |  |  | or die "Unknown transport '$transport'"; | 
| 82 | 0 | 0 |  |  |  |  | eval "require  $plugin" or die $@; | 
| 83 | 0 | 0 |  |  |  |  | $plugin->_make_sync if $plugin->can('_make_sync'); | 
| 84 | 0 |  |  |  |  |  | my $protocol = $plugin->protocol; | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 0 | 0 |  |  |  |  | my %config = ( | 
| 87 |  |  |  |  |  |  | cluster => { name => 'es_test' }, | 
| 88 |  |  |  |  |  |  | gateway => { type => 'local', expected_nodes => $instances }, | 
| 89 |  |  |  |  |  |  | network => { host => 'localhost' }, | 
| 90 |  |  |  |  |  |  | "$protocol.port" => "$port-" . ( $port + $instances - 1 ), | 
| 91 | 0 |  |  |  |  |  | %{ $params{config} || {} } | 
| 92 |  |  |  |  |  |  | ); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 0 |  |  |  |  |  | my $ip = $config{network}{host} = delete $params{ip}; | 
| 95 | 0 |  |  |  |  |  | my @servers = map {"$ip:$_"} $port .. $port + $instances - 1; | 
|  | 0 |  |  |  |  |  |  | 
| 96 | 0 |  |  |  |  |  | my @publish = map {"$ip:$_"} 9300 .. 9300 + $instances - 1; | 
|  | 0 |  |  |  |  |  |  | 
| 97 | 0 |  |  |  |  |  | $config{'discovery.zen.ping.unicast.hosts'} = \@publish; | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 0 |  |  |  |  |  | foreach (@servers) { | 
| 100 | 0 | 0 |  |  |  |  | if ( IO::Socket::INET->new($_) ) { | 
| 101 | 0 |  |  |  |  |  | die < | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | ************************************************************ | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | There is already a server running on $_. | 
| 106 |  |  |  |  |  |  | Please shut it down before starting the test server | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | ************************************************************ | 
| 109 |  |  |  |  |  |  | RUNNING | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 0 |  |  |  |  |  | my $server = $servers[0]; | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 0 |  |  |  |  |  | print "Starting test server installed in $home\n"; | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 0 |  |  |  |  |  | my $cmd          = catfile( $home, 'bin', 'elasticsearch' ); | 
| 118 | 0 |  |  |  |  |  | my $pid_file     = File::Temp->new; | 
| 119 | 0 |  |  |  |  |  | my $blank_config = File::Temp->new( SUFFIX => '.yml' ); | 
| 120 | 0 |  |  |  |  |  | my $config_path  = $blank_config->filename(); | 
| 121 |  |  |  |  |  |  |  | 
| 122 | 0 |  |  |  |  |  | my $dir     = ''; | 
| 123 | 0 |  |  |  |  |  | my $dirname = ''; | 
| 124 | 0 |  |  |  |  |  | my $PIDs    = []; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 0 | 0 |  |  |  |  | unless ( $config{path}{data} ) { | 
| 127 | 0 |  |  |  |  |  | $dir = File::Temp->newdir( | 
| 128 |  |  |  |  |  |  | 'elastic_XXXXX', | 
| 129 |  |  |  |  |  |  | CLEANUP => 0, | 
| 130 |  |  |  |  |  |  | TMPDIR  => 1 | 
| 131 |  |  |  |  |  |  | ); | 
| 132 | 0 |  |  |  |  |  | $dirname = $config{path}{data} = $dir->dirname; | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 0 |  |  |  |  |  | my $old_SIGINT = $SIG{INT}; | 
| 136 |  |  |  |  |  |  | my $new_SIGINT = sub { | 
| 137 | 0 |  |  | 0 |  |  | $class->_shutdown_servers( $PIDs, $dirname ); | 
| 138 | 0 | 0 |  |  |  |  | if ( ref $old_SIGINT eq 'CODE' ) { | 
| 139 | 0 |  |  |  |  |  | return $old_SIGINT->(); | 
| 140 |  |  |  |  |  |  | } | 
| 141 | 0 |  |  |  |  |  | exit(1); | 
| 142 | 0 |  |  |  |  |  | }; | 
| 143 | 0 |  |  |  |  |  | $SIG{INT} = $new_SIGINT; | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 0 |  |  |  |  |  | DumpFile( $blank_config->filename, \%config ); | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 0 |  |  |  |  |  | for ( 1 .. $instances ) { | 
| 148 | 0 |  |  |  |  |  | print "Starting test node $_\n"; | 
| 149 | 0 |  |  |  |  |  | my $int_caught = 0; | 
| 150 | 0 |  |  | 0 |  |  | local $SIG{INT} = sub { $int_caught++; }; | 
|  | 0 |  |  |  |  |  |  | 
| 151 | 0 | 0 |  |  |  |  | defined( my $pid = fork ) or die "Couldn't fork a new process: $!"; | 
| 152 | 0 | 0 |  |  |  |  | if ( $pid == 0 ) { | 
| 153 | 0 | 0 |  |  |  |  | die "Can't start a new session: $!" if setsid == -1; | 
| 154 | 0 |  |  |  |  |  | exec( $cmd, '-p', $pid_file->filename, | 
| 155 |  |  |  |  |  |  | '-Des.config=' . $config_path ); | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  | else { | 
| 158 | 0 |  |  |  |  |  | sleep 1; | 
| 159 | 0 |  |  |  |  |  | open my $pid_fh, '<', $pid_file->filename; | 
| 160 | 0 |  |  |  |  |  | my $pid = <$pid_fh>; | 
| 161 | 0 | 0 |  |  |  |  | die "ES is running, but no PID found" unless $pid; | 
| 162 | 0 |  |  |  |  |  | chomp $pid; | 
| 163 | 0 |  |  |  |  |  | push @$PIDs, $pid; | 
| 164 |  |  |  |  |  |  | } | 
| 165 | 0 | 0 |  |  |  |  | $new_SIGINT->() if $int_caught; | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 0 |  |  |  |  |  | print "Waiting for servers to warm up\n"; | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 0 |  |  |  |  |  | my $timeout = 20; | 
| 171 | 0 |  |  |  |  |  | while (@servers) { | 
| 172 | 0 | 0 |  |  |  |  | if ( IO::Socket::INET->new( $servers[0] ) ) { | 
| 173 | 0 |  |  |  |  |  | print "Node running on $servers[0]\n"; | 
| 174 | 0 |  |  |  |  |  | shift @servers; | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | else { | 
| 177 | 0 |  |  |  |  |  | sleep 1; | 
| 178 |  |  |  |  |  |  | } | 
| 179 | 0 |  |  |  |  |  | $timeout--; | 
| 180 | 0 | 0 |  |  |  |  | last if $timeout == 0; | 
| 181 |  |  |  |  |  |  | } | 
| 182 | 0 | 0 |  |  |  |  | if (@servers) { | 
| 183 | 0 |  |  |  |  |  | eval { $class->_shutdown_servers( $PIDs, $dirname ) }; | 
|  | 0 |  |  |  |  |  |  | 
| 184 | 0 |  |  |  |  |  | die "Couldn't start $instances nodes for transport $transport"; | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 0 |  |  |  |  |  | my $es = eval { | 
| 188 | 0 |  |  |  |  |  | $class->SUPER::new( | 
| 189 |  |  |  |  |  |  | %params, | 
| 190 |  |  |  |  |  |  | servers     => $server, | 
| 191 |  |  |  |  |  |  | trace_calls => $params{trace_calls}, | 
| 192 |  |  |  |  |  |  | transport   => $transport, | 
| 193 |  |  |  |  |  |  | pids        => $PIDs, | 
| 194 |  |  |  |  |  |  | tmpdir      => $dirname, | 
| 195 |  |  |  |  |  |  | ); | 
| 196 |  |  |  |  |  |  | }; | 
| 197 | 0 | 0 |  |  |  |  | unless ($es) { | 
| 198 | 0 |  |  |  |  |  | my $error = $@; | 
| 199 | 0 |  |  |  |  |  | $class->_shutdown_servers( $PIDs, $dirname ); | 
| 200 | 0 |  |  |  |  |  | die $error; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 | 0 |  |  |  |  |  | my $attempts = 20; | 
| 204 | 0 |  |  |  |  |  | while (1) { | 
| 205 | 0 | 0 |  |  |  |  | eval { @{ $es->refresh_servers } == $instances } && last; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 206 | 0 | 0 |  |  |  |  | die("**** Couldn't connect to ElasticSearch at $server ****\n") | 
| 207 |  |  |  |  |  |  | unless --$attempts; | 
| 208 | 0 |  |  |  |  |  | print "Connection failed. Retrying\n"; | 
| 209 | 0 |  |  |  |  |  | sleep 1; | 
| 210 |  |  |  |  |  |  | } | 
| 211 | 0 |  |  |  |  |  | print "Connected\n"; | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 0 |  |  |  |  |  | return $es; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | #=================================== | 
| 217 |  |  |  |  |  |  | sub pids { | 
| 218 |  |  |  |  |  |  | #=================================== | 
| 219 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 220 | 0 | 0 |  |  |  |  | if (@_) { | 
| 221 | 0 |  |  |  |  |  | $self->{_pids} = shift; | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 0 |  |  |  |  |  | return $self->{_pids}; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | #=================================== | 
| 227 |  |  |  |  |  |  | sub tmpdir { | 
| 228 |  |  |  |  |  |  | #=================================== | 
| 229 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 230 | 0 | 0 |  |  |  |  | if (@_) { | 
| 231 | 0 |  |  |  |  |  | $self->{_tmpdir} = shift; | 
| 232 |  |  |  |  |  |  | } | 
| 233 | 0 |  |  |  |  |  | return $self->{_tmpdir}; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | #=================================== | 
| 237 |  |  |  |  |  |  | sub _shutdown_servers { | 
| 238 |  |  |  |  |  |  | #=================================== | 
| 239 | 0 |  |  | 0 |  |  | my ( $self, $PIDs, $dir ) = @_; | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 0 |  |  |  |  |  | local $?; | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 0 | 0 |  |  |  |  | $PIDs = $self->pids   unless defined $PIDs; | 
| 244 | 0 | 0 |  |  |  |  | $dir  = $self->tmpdir unless defined $dir; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 0 | 0 |  |  |  |  | return unless $PIDs; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 0 |  |  |  |  |  | kill 9, @$PIDs; | 
| 249 | 0 |  |  |  |  |  | sleep 1; | 
| 250 |  |  |  |  |  |  |  | 
| 251 | 0 | 0 |  |  |  |  | while (1) { last if wait == -1 } | 
|  | 0 |  |  |  |  |  |  | 
| 252 | 0 | 0 |  |  |  |  | if ( defined $dir ) { | 
| 253 | 0 |  |  |  |  |  | rmtree( $dir, { error => \my $error } ); | 
| 254 |  |  |  |  |  |  | } | 
| 255 | 0 |  |  |  |  |  | undef $dir; | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 | 0 |  |  | 0 |  |  | sub DESTROY { shift->_shutdown_servers; } | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =head1 AUTHOR | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | Clinton Gormley, Eclinton@traveljury.comE | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | Copyright (C) 2011 by Clinton Gormley | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | This library is free software; you can redistribute it and/or modify | 
| 269 |  |  |  |  |  |  | it under the same terms as Perl itself, either Perl version 5.8.7 or, | 
| 270 |  |  |  |  |  |  | at your option, any later version of Perl 5 you may have available. | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | =cut | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | 1 |