File Coverage

blib/lib/Search/Elasticsearch/TestServer.pm
Criterion Covered Total %
statement 24 105 22.8
branch 0 34 0.0
condition 0 3 0.0
subroutine 8 19 42.1
pod 0 5 0.0
total 32 166 19.2


line stmt bran cond sub pod time code
1             # Licensed to Elasticsearch B.V. under one or more contributor
2             # license agreements. See the NOTICE file distributed with
3             # this work for additional information regarding copyright
4             # ownership. Elasticsearch B.V. licenses this file to you under
5             # the Apache License, Version 2.0 (the "License"); you may
6             # not use this file except in compliance with the License.
7             # You may obtain a copy of the License at
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing,
12             # software distributed under the License is distributed on an
13             # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14             # KIND, either express or implied. See the License for the
15             # specific language governing permissions and limitations
16             # under the License.
17              
18             package Search::Elasticsearch::TestServer;
19             $Search::Elasticsearch::TestServer::VERSION = '8.00';
20 2     2   1053 use Moo;
  2         6  
  2         14  
21 2     2   661 use Search::Elasticsearch();
  2         12  
  2         52  
22 2     2   11 use POSIX 'setsid';
  2         27  
  2         19  
23 2     2   144 use File::Temp();
  2         4  
  2         36  
24 2     2   976 use IO::Socket();
  2         25037  
  2         55  
25 2     2   1346 use HTTP::Tiny;
  2         39227  
  2         85  
26              
27 2     2   16 use Search::Elasticsearch::Util qw(parse_params throw);
  2         4  
  2         20  
28 2     2   777 use namespace::clean;
  2         4  
  2         15  
29              
30             has 'es_home' => ( is => 'ro', default => $ENV{ES_HOME} );
31             has 'es_version' => ( is => 'ro', default => $ENV{ES_VERSION} );
32             has 'instances' => ( is => 'ro', default => 1 );
33             has 'http_port' => ( is => 'ro', default => 9600 );
34             has 'es_port' => ( is => 'ro', default => 9700 );
35             has 'pids' => (
36             is => 'ro',
37             default => sub { [] },
38             clearer => 1,
39             predicate => 1
40             );
41              
42             has 'dirs' => ( is => 'ro', default => sub { [] } );
43             has 'conf' => ( is => 'ro', default => sub { [] } );
44             has '_starter_pid' => ( is => 'rw', required => 0, predicate => 1 );
45              
46             #===================================
47             sub start {
48             #===================================
49 0     0 0   my $self = shift;
50              
51 0 0         my $home = $self->es_home
52             or throw( 'Param', "Missing required param " );
53 0 0         $self->es_version
54             or throw( 'Param', "Missing required param " );
55              
56 0           my $instances = $self->instances;
57 0           my $port = $self->http_port;
58 0           my $es_port = $self->es_port;
59 0           my @http = map { $port++ } ( 1 .. $instances );
  0            
60 0           my @transport = map { $es_port++ } ( 1 .. $instances );
  0            
61              
62 0           $self->_check_ports( @http, @transport );
63              
64 0           my $old_SIGINT = $SIG{INT};
65             $SIG{INT} = sub {
66 0     0     $self->shutdown;
67 0 0         if ( ref $old_SIGINT eq 'CODE' ) {
68 0           return $old_SIGINT->();
69             }
70 0           exit(1);
71 0           };
72              
73 0           for ( 0 .. $instances - 1 ) {
74 0           my $dir = File::Temp->newdir();
75 0           push @{ $self->dirs }, $dir;
  0            
76 0           print "Starting node: http://127.0.0.1:$http[$_]\n";
77 0           $self->_start_node( $dir, $transport[$_], $http[$_] );
78             }
79              
80 0           $self->_check_nodes(@http);
81 0           return [ map {"http://127.0.0.1:$_"} @http ];
  0            
82             }
83              
84             #===================================
85             sub _check_ports {
86             #===================================
87 0     0     my $self = shift;
88 0           for my $port (@_) {
89 0 0         next unless IO::Socket::INET->new("127.0.0.1:$port");
90 0           throw( 'Param',
91             "There is already a service running on 127.0.0.1:$port. "
92             . "Please shut it down before starting the test server" );
93             }
94             }
95              
96             #===================================
97             sub _check_nodes {
98             #===================================
99 0     0     my $self = shift;
100 0           my $http = HTTP::Tiny->new;
101 0           for my $node (@_) {
102 0           print "Checking node: http://127.0.0.1:$node\n";
103 0           my $i = 20;
104 0           while (1) {
105             last
106 0 0         if $http->head("http://127.0.0.1:$node/")->{status} == 200;
107 0 0         throw( 'Cxn', "Couldn't connect to http://127.0.0.1:$node" )
108             unless $i--;
109 0           sleep 1;
110             }
111              
112             }
113             }
114              
115             #===================================
116             sub _start_node {
117             #===================================
118 0     0     my ( $self, $dir, $transport, $http ) = @_;
119              
120 0           my $pid_file = File::Temp->new;
121 0           my @config = $self->_command_line( $pid_file, $dir, $transport, $http );
122              
123 0           my $int_caught = 0;
124             {
125 0     0     local $SIG{INT} = sub { $int_caught++; };
  0            
  0            
126 0 0         defined( my $pid = fork )
127             or throw( 'Internal', "Couldn't fork a new process: $!" );
128 0 0         if ( $pid == 0 ) {
129 0 0         throw( 'Internal', "Can't start a new session: $!" )
130             if setsid == -1;
131 0 0         exec(@config) or die "Couldn't execute @config: $!";
132             }
133             else {
134 0           for ( 1 .. 5 ) {
135 0 0         last if -s $pid_file->filename();
136 0           sleep 1;
137             }
138 0           open my $pid_fh, '<', $pid_file->filename;
139 0           my $pid = <$pid_fh>;
140 0 0         throw( 'Internal', "No PID file found for Elasticsearch" )
141             unless $pid;
142 0           chomp $pid;
143 0           push @{ $self->{pids} }, $pid;
  0            
144 0           $self->_starter_pid($$);
145             }
146             }
147 0 0         $SIG{INT}->('INT') if $int_caught;
148             }
149              
150             #===================================
151             sub guarded_shutdown {
152             #===================================
153 0     0 0   my $self = shift;
154 0 0 0       if ( $self->_has_starter_pid && $$ == $self->_starter_pid ) {
155 0           $self->shutdown();
156             }
157             }
158              
159             #===================================
160             sub shutdown {
161             #===================================
162 0     0 0   my $self = shift;
163 0           local $?;
164              
165 0 0         return unless $self->has_pids;
166              
167 0           my $pids = $self->pids;
168 0           $self->clear_pids;
169 0 0         return unless @$pids;
170              
171 0           kill 9, @$pids;
172 0           $self->clear_dirs;
173             }
174              
175             #===================================
176             sub _command_line {
177             #===================================
178 0     0     my ( $self, $pid_file, $dir, $transport, $http ) = @_;
179              
180 0           my $version = $self->es_version;
181 0           my $class = "Search::Elasticsearch::Client::${version}::TestServer";
182 0 0         eval "require $class" || die $@;
183              
184 0           return $class->command_line(@_);
185             }
186              
187             #===================================
188             sub clear_dirs {
189             #===================================
190 0     0 0   my $self = shift;
191 0           @{ $self->dirs() } = ();
  0            
192             }
193              
194             #===================================
195 0     0 0   sub DEMOLISH { shift->guarded_shutdown }
196             #===================================
197              
198             1;
199              
200             # ABSTRACT: A helper class to launch Elasticsearch nodes
201              
202             __END__