File Coverage

blib/lib/ElasticSearch/TestServer.pm
Criterion Covered Total %
statement 33 138 23.9
branch 2 54 3.7
condition 1 5 20.0
subroutine 11 17 64.7
pod 1 3 33.3
total 48 217 22.1


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