Skip to content

Commit 61caf58

Browse files
authored
Merge pull request #1045 from bodo-hugo-barwich/no-47_wrong-index
ElasticSearch Availabilty Check and Mapping Self-Check
2 parents 50332b1 + 2ea1921 commit 61caf58

File tree

2 files changed

+464
-18
lines changed

2 files changed

+464
-18
lines changed

lib/MetaCPAN/Role/Script.pm

+245-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ElasticSearchX::Model::Document::Types qw(:all);
66
use Git::Helpers qw( checkout_root );
77
use Log::Contextual qw( :log :dlog );
88
use MetaCPAN::Model ();
9-
use MetaCPAN::Types::TypeTiny qw( Bool Int Path Str );
9+
use MetaCPAN::Types::TypeTiny qw( Bool HashRef Int Path Str );
1010
use Mojo::Server ();
1111
use Term::ANSIColor qw( colored );
1212
use IO::Interactive qw( is_interactive );
@@ -35,6 +35,22 @@ has die_on_error => (
3535
documentation => 'Die on errors instead of simply logging',
3636
);
3737

38+
has exit_code => (
39+
isa => Int,
40+
is => 'rw',
41+
default => 0,
42+
documentation => 'Exit Code to be returned on termination',
43+
);
44+
45+
has arg_await_timeout => (
46+
init_arg => 'await',
47+
is => 'ro',
48+
isa => Int,
49+
default => 15,
50+
documentation =>
51+
'seconds before connection is considered failed with timeout',
52+
);
53+
3854
has ua => (
3955
is => 'ro',
4056
lazy => 1,
@@ -71,6 +87,27 @@ has index => (
7187
'Index to use, defaults to "cpan" (when used: also export ES_SCRIPT_INDEX)',
7288
);
7389

90+
has cluster_info => (
91+
isa => HashRef,
92+
traits => ['Hash'],
93+
is => 'rw',
94+
default => sub { {} },
95+
);
96+
97+
has indices_info => (
98+
isa => HashRef,
99+
traits => ['Hash'],
100+
is => 'rw',
101+
default => sub { {} },
102+
);
103+
104+
has aliases_info => (
105+
isa => HashRef,
106+
traits => ['Hash'],
107+
is => 'rw',
108+
default => sub { {} },
109+
);
110+
74111
has port => (
75112
isa => Int,
76113
is => 'ro',
@@ -123,13 +160,27 @@ sub BUILDARGS {
123160
}
124161

125162
sub handle_error {
126-
my ( $self, $error ) = @_;
163+
my ( $self, $error, $die_always ) = @_;
164+
165+
# Die if configured (for the test suite).
166+
$die_always = $self->die_on_error unless defined $die_always;
127167

128168
# Always log.
129169
log_fatal {$error};
130170

131-
# Die if configured (for the test suite).
132-
Carp::croak $error if $self->die_on_error;
171+
$! = $self->exit_code if ( $self->exit_code != 0 );
172+
173+
Carp::croak $error if $die_always;
174+
}
175+
176+
sub print_error {
177+
my ( $self, $error ) = @_;
178+
179+
# Always log.
180+
log_error {$error};
181+
182+
# Display Error in red
183+
print colored( ['bold red'], "*** ERROR ***: $error" ), "\n";
133184
}
134185

135186
sub index {
@@ -195,6 +246,122 @@ before run => sub {
195246
#Dlog_debug {"Connected to $_"} $self->remote;
196247
};
197248

249+
sub _get_indices_info {
250+
my ( $self, $irefresh ) = @_;
251+
252+
if ( $irefresh || scalar( keys %{ $self->indices_info } ) == 0 ) {
253+
my $sinfo_rs = $self->es->cat->indices( h => [ 'index', 'health' ] );
254+
my $sindices_parsing = qr/^([^[:space:]]+) +([^[:space:]]+)/m;
255+
256+
$self->indices_info( {} );
257+
258+
while ( $sinfo_rs =~ /$sindices_parsing/g ) {
259+
$self->indices_info->{$1}
260+
= { 'index_name' => $1, 'health' => $2 };
261+
}
262+
}
263+
}
264+
265+
sub _get_aliases_info {
266+
my ( $self, $irefresh ) = @_;
267+
268+
if ( $irefresh || scalar( keys %{ $self->aliases_info } ) == 0 ) {
269+
my $sinfo_rs = $self->es->cat->aliases( h => [ 'alias', 'index' ] );
270+
my $saliases_parsing = qr/^([^[:space:]]+) +([^[:space:]]+)/m;
271+
272+
$self->aliases_info( {} );
273+
274+
while ( $sinfo_rs =~ /$saliases_parsing/g ) {
275+
$self->aliases_info->{$1} = { 'alias_name' => $1, 'index' => $2 };
276+
}
277+
}
278+
}
279+
280+
sub check_health {
281+
my ( $self, $irefresh ) = @_;
282+
my $ihealth = 0;
283+
284+
$irefresh = 0 unless ( defined $irefresh );
285+
286+
$ihealth = $self->await;
287+
288+
if ($ihealth) {
289+
$self->_get_indices_info($irefresh);
290+
291+
foreach ( keys %{ $self->indices_info } ) {
292+
$ihealth = 0
293+
if ( $self->indices_info->{$_}->{'health'} eq 'red' );
294+
}
295+
}
296+
297+
if ($ihealth) {
298+
$self->_get_aliases_info($irefresh);
299+
300+
$ihealth = 0 if ( scalar( keys %{ $self->aliases_info } ) == 0 );
301+
}
302+
303+
return $ihealth;
304+
}
305+
306+
sub await {
307+
my $self = $_[0];
308+
my $iready = 0;
309+
310+
if ( scalar( keys %{ $self->cluster_info } ) == 0 ) {
311+
my $es = $self->es;
312+
my $iseconds = 0;
313+
314+
log_info {"Awaiting Elasticsearch ..."};
315+
316+
do {
317+
eval {
318+
$iready = $es->ping;
319+
320+
if ($iready) {
321+
log_info {
322+
"Awaiting $iseconds / "
323+
. $self->arg_await_timeout
324+
. " : ready"
325+
};
326+
327+
$self->cluster_info( \%{ $es->info } );
328+
}
329+
};
330+
331+
if ($@) {
332+
if ( $iseconds < $self->arg_await_timeout ) {
333+
log_info {
334+
"Awaiting $iseconds / "
335+
. $self->arg_await_timeout
336+
. " : unavailable - sleeping ..."
337+
};
338+
339+
sleep(1);
340+
341+
$iseconds++;
342+
}
343+
else {
344+
log_error {
345+
"Awaiting $iseconds / "
346+
. $self->arg_await_timeout
347+
. " : unavailable - timeout!"
348+
};
349+
350+
#Set System Error: 112 - EHOSTDOWN - Host is down
351+
$self->exit_code(112);
352+
$self->handle_error( $@, 1 );
353+
}
354+
}
355+
} while ( !$iready && $iseconds <= $self->arg_await_timeout );
356+
}
357+
else {
358+
#ElasticSearch Service is available
359+
$iready = 1;
360+
}
361+
362+
return $iready;
363+
}
364+
198365
sub are_you_sure {
199366
my ( $self, $msg ) = @_;
200367

@@ -216,8 +383,82 @@ __END__
216383
217384
=pod
218385
386+
=head1 NAME
387+
388+
MetaCPAN::Role::Script - Base Role which is used by many command line applications
389+
219390
=head1 SYNOPSIS
220391
221392
Roles which should be available to all modules.
222393
394+
=head1 OPTIONS
395+
396+
This Role makes the command line application accept the following options
397+
398+
=over 4
399+
400+
=item Option C<--await 15>
401+
402+
This option will set the I<ElasticSearch Availability Check Timeout>.
403+
After C<await> seconds the Application will fail with an Exception and the Exit Code [112]
404+
(C<112 - EHOSTDOWN - Host is down>) will be returned
405+
406+
bin/metacpan <script_name> --await 15
407+
408+
See L<Method C<await()>>
409+
410+
=back
411+
412+
=head1 METHODS
413+
414+
This Role provides the following methods
415+
416+
=over 4
417+
418+
=item C<await()>
419+
420+
This method uses the
421+
L<C<Search::Elasticsearch::Client::2_0::Direct::ping()>|https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#ping()>
422+
method to verify the service availabilty and wait for C<arg_await_timeout> seconds.
423+
When the service does not become available within C<arg_await_timeout> seconds it re-throws the
424+
Exception from the C<Search::Elasticsearch::Client> and sets C< $! > to C< 112 >.
425+
The C<Search::Elasticsearch::Client> generates a C<"Search::Elasticsearch::Error::NoNodes"> Exception.
426+
When the service is available it will populate the C<cluster_info> C<HASH> structure with the basic information
427+
about the cluster.
428+
See L<Option C<--await 15>>
429+
See L<Method C<check_health()>>
430+
431+
=item C<check_health( [ refresh ] )>
432+
433+
This method uses the
434+
L<C<Search::Elasticsearch::Client::2_0::Direct::cat()>|https://metacpan.org/pod/Search::Elasticsearch::Client::2_0::Direct#cat()>
435+
method to collect basic data about the cluster structure as the general information,
436+
the health state of the indices and the created aliases.
437+
This information is stored in C<cluster_info>, C<indices_info> and C<aliases_info> as C<HASH> structures.
438+
If the parameter C<refresh> is set to C< 1 > the structures C<indices_info> and C<aliases_info> will always
439+
be updated.
440+
If the C<cluster_info> structure is empty it calls first the C<await()> method.
441+
If the service is unavailable the C<await()> method will produce an exception and the structures will be empty
442+
The method returns C< 1 > when the C<cluster_info> is populated, none of the indices in C<indices_info> has
443+
the Health State I<red> and at least one alias is created in C<aliases_info>
444+
otherwise the method returns C< 0 >
445+
446+
=item C<are_you_sure()>
447+
448+
Requests the user to confirm the operation with "I< YES >"
449+
450+
=item C<handle_error( error_message[, die_always ] )>
451+
452+
Logs the string C<error_message> with the log function as fatal error.
453+
If C<exit_code> is not equel C< 0 > sets its value in C< $! >.
454+
If the option C<--die_on_error> is enabled it throws an Exception with C<error_message>.
455+
If the parameter C<die_always> is set it overrides the option C<--die_on_error>.
456+
457+
=item C<print_error( error_message )>
458+
459+
Logs the string C<error_message> with the log function and displays it in red.
460+
But it does not end the application.
461+
462+
=back
463+
223464
=cut

0 commit comments

Comments
 (0)