# # Copyright (c) 2014-20* NetApp Inc. # All rights reserved. # ## @summary Interface class for various health checkers defined ## inside lib/NACL/MTask/HealthChecker/ dir. ## It is NOT RECOMMENDED to create direct object of this class. ## ## @author dl-nacl-dev@netapp.com ## @status Shared ## @pod here package NACL::MTask::HealthChecker; use Class::Inspector; use NACL::C::Node; use Data::Dumper; use Scalar::Util qw(blessed); use feature 'state'; use Params::Validate qw(validate_with OBJECT SCALAR ARRAYREF); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NATE::Result; use NATE::Messenger qw(messenger_global); use NATE::Log qw(log_global); use NACL::APISet::Exceptions::ResponseException qw(:try); use NATE::BaseException; use NACL::AppBuilder::Exceptions::HealthChecker; use NACL::AppBuilder::HealthChecker::Driver qw($MESSENGER_ZEROMQ_PORT); use Class::MethodMaker [ array => 'nodes', scalar => 'process', scalar => 'name', scalar => [{-default => 0 }, 'is_failed'], scalar => [{-default => 1 }, 'is_enabled'], ]; =head1 METHODS =head2 new my $hc = NACL::MTask::HealthChecker::SomeHealthChecker->new( nodes => \@nodes, ); (Not recommended to call directly. Call through HealthChecker subclass) Creates a HealthChecker object. =over =item Options =over =item C<< nodes => @nodes_list >> (Optional) Each element of the list can either be L OR node name. If its node name then corresponding nate_hosts file should exist. If nodes are not passed then health checker will run on all nodes passed to NACL::AppBuilder::HealthChecker::Driver. If you are not using Driver to run health checker, then you should pass nodes to this method. =back =item Returns Returns object of type NACL::MTask::HealthChecker::. =back =back =cut sub new { $Log->enter() if $may_enter; my ($class, @args) = @_; state $spec = { nodes => { type => ARRAYREF, optional => 1}, }; my %opts = validate_with( params => \@args, spec => $spec, ); my $self = {}; bless($self, $class); if (defined $opts{nodes}) { my @nodes = @{$opts{nodes}}; my @node_objs; foreach my $node (@nodes) { if (blessed($node)) { if ($node->isa("NACL::C::CommandInterface::ONTAP")) { push(@node_objs, $node); } else { my $error = "Object passed should be of type 'NACL::C::CommandInterface::ONTAP' " . "But it was of type "; NATE::BaseException->throw($error . ref($node)); } } else { push(@node_objs, NACL::C::Node->new(name => $node)); } } $self->nodes(@node_objs); } $Log->exit() if $may_exit; return $self; } =head2 start $HC->start(); Method to execute all health checker methods whose name start with 'health_check_'. Override for this method can be provided in subclass if you want to all execute hc methods on all nodes in parallel. =cut sub start { my ($self, @args) = @_; state $spec = { execute_in_parallel => {type => SCALAR, default => 1}, command_interfaces => {type => ARRAYREF, optional => 1}, }; my %opts = validate_with( spec => $spec, params => \@args, ); my @cis; if (defined $opts{command_interfaces}) { @cis = @{$opts{command_interfaces}}; } else { @cis = $self->nodes(); } my $coderef = sub { my ($hc_obj, $ci) = @_; my @methods = $hc_obj->_get_all_hc_methods(); foreach my $method (@methods) { my $start_msg = $hc_obj->start_msg($method); $hc_obj->_display_start_msg_for_hc_method(msg => $start_msg); $hc_obj->execute_hc(method => $method, command_interface => $ci); } $hc_obj->_write_result_to_parent(); if ($hc_obj->is_failed) { my $failed_hc_methods = $hc_obj->fail_result(); my $error_str = ""; my $i = 1; foreach my $error (@$failed_hc_methods) { my $client = defined($error->{node}) ? $error->{node} : $error->{cluster}; $error_str .= $i . ". HC Method: " . $error->{method} . "\n" . "\tError: " . $error->{error} . "\n" . "\tNode: " . $client . "\n"; $i++; } NACL::AppBuilder::Exceptions::HealthChecker->throw( "At least one of the health checker method reported failure. " . "List of failed hc methods\n" . $error_str ); } }; foreach my $ci (@cis) { my $runid = $self->_get_name($ci); my $proc = NATE::Process->new( codespec => $coderef, args => [$self,$ci], background => $opts{execute_in_parallel}, runid => $runid, onexit => \&NATE::Process::on_exit_propagate_worst_result, ); $proc->start(); push(@procs, $proc); } foreach my $proc(@procs) { $proc->wait(); } } sub _get_all_hc_methods { my ($self) = @_; my $methods = Class::Inspector->methods( ref($self), 'public' ); my @hc_methods; foreach my $method (@{$methods}) { if ($method =~ /^check_health_/) { push(@hc_methods, $method); } } return @hc_methods; } =head2 execute_hc $HC->execute_hc(method => $method); Executes method $method and updates the result =over =item Options =over =item C<< method => $method_name >> (Required) Name of the health checker method to be executed. =back =back =cut sub execute_hc { my ($self, @args) = @_; state $spec = { method => {type => SCALAR}, command_interface => {type => OBJECT, isa => "NACL::C::CommandInterface"}, }; my %opts = validate_with( params => \@args, spec => $spec, ); my $method = delete $opts{method}; my $ci = delete $opts{command_interface}; my %update_opts = ( method => $method, command_interface => $ci, ); try { $self->$method(command_interface => $ci); $self->update_result(%update_opts); } catch NATE::BaseException with { my $ex = shift(); $self->update_result(exception => $ex, %update_opts); }; } =head2 update_result $HC->update_result(method => $method); Update the result for method $method on hc object. =over =item Options =over =item C<< method => $method_name >> (Required) Name of the health checker method to be executed. =item C<< exception => $ex >> (Optional) Required in case method reported a failure Exceptions thrown by the method. =back =back =cut sub update_result { my ($self, @args) = @_; state $spec = { method => {type => SCALAR}, command_interface => {type => OBJECT}, exception => {type => OBJECT, optional => 1}, }; my %opts = validate_with( params => \@args, spec => $spec, ); my $method = $opts{method}; my $ci = $opts{command_interface}; my $name = $self->_get_name($ci); my $result = 'PASS'; my $list = { method => $method, }; if($ci->isa("NACL::C::Cserver")) { $list->{cluster} = $name; } else { $list->{node} = $name; } if(defined $opts{exception}) { $result = 'FAIL'; $list->{error} = $opts{exception}->text; # At least one of the hc method failed, Marking # HealthChecker as failed. $self->is_failed(1); } push(@{$self->{result}->{$result}}, $list); } =head2 result $HC->result(); Returns result stored on HC object. =item Returns =over Returns result hashref in the following format. Result => { PASS => { [checker_method1, checker_method1] }, FAIL => { [ {checker_method1 => 'Error Message for checker_method1}, {checker_method2 => 'Error Message for checker_method2} ] } }, =back =cut sub result { my ($self) = @_; my $result = $self->{result} || {}; return $result; } =head2 fail_result $HC->fail_result(); Returns failed results stored on HC object. =item Returns =over Returns(arrayref) failed result in the following format. Result => [ {checker_method1 => 'Error Message for checker_method1}, {checker_method2 => 'Error Message for checker_method2} ] =back =cut sub fail_result { my ($self) = @_; my $failed_result = $self->{result}->{FAIL}; return $failed_result; } =head2 pass_result $HC->pass_result(); Returns passed results stored on HC object. =item Returns =over Returns(arrayref) passed result in the following format. Result => [ checker_method1, checker_method2 ] =back =cut sub pass_result { my ($self) = @_; my $passed_result = $self->{result}->{PASS}; return $passed_result; } sub _write_result_to_parent { my ($self, @args) = @_; my $log = log_global(); $may_enter = $Log->may_enter(); $may_exit = $Log->may_exit(); my $msnger = messenger_global(transport => 'zeromq'); my $msg_type = "NACL_HealthChecker_" . $self->name(); # send a message to somebody listening at tcp://localhost:54321 $msnger->put( endpoint => { protocol => 'tcp', address => 'localhost', port => $MESSENGER_ZEROMQ_PORT, }, type => $msg_type, message => $self->result(), ); } sub _get_name { my ($self, $ci) = @_; return $ci->isa("NACL::C::Cserver") ? $ci->cserver: $ci->node(); } sub _display_start_msg_for_hc_method { my ($self, @args) = @_; state $spec = { msg => {type => SCALAR, defautl => "Executing new health checker method"}, }; my %opts = validate_with( spec => $spec, params => \@args, ); my $msg = $opts{msg}; $Log->comment('#' x 80); $Log->comment($msg); $Log->comment('#' x 80); } sub _get_msg { my ($self, @args) = @_; state $spec = { method => {type => SCALAR,}, }; my %opts = validate_with( spec => $spec, params => \@args, ); my $method = $opts{method}; my $start_msg = $self->start_msg(); if (!defined $start_msg) { $msg = "Executing new health checker method"; } else { $msg = $start_msg->{$method}; } return $msg; } sub start_msg { my ($self, $method) = @_; return "Executing new health checker method: $method"; } sub _report_error { my ($self, $msg) = @_; $Log->comment($msg); NACL::AppBuilder::Exceptions::HealthChecker->throw($msg); } 1;