Skip to content

Commit dace831

Browse files
Methods await() and check_health()
1 parent 50332b1 commit dace831

File tree

1 file changed

+237
-4
lines changed

1 file changed

+237
-4
lines changed

lib/MetaCPAN/Role/Script.pm

Lines changed: 237 additions & 4 deletions
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,114 @@ before run => sub {
195246
#Dlog_debug {"Connected to $_"} $self->remote;
196247
};
197248

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

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

0 commit comments

Comments
 (0)