From 6e2a0a5d1ce81f39e664d2d86a86ba5d6f229a9a Mon Sep 17 00:00:00 2001 From: qgarnier Date: Mon, 9 Aug 2021 16:05:39 +0200 Subject: [PATCH] enh(aws/ec2): instances status mode - add uptime option (#3029) --- cloud/aws/custom/awscli.pm | 26 ++- cloud/aws/custom/paws.pm | 27 +++ cloud/aws/ec2/mode/instancesstatus.pm | 306 ++++++++++++++++---------- 3 files changed, 241 insertions(+), 118 deletions(-) diff --git a/cloud/aws/custom/awscli.pm b/cloud/aws/custom/awscli.pm index 8aa9c05fc..2ae0de6d6 100644 --- a/cloud/aws/custom/awscli.pm +++ b/cloud/aws/custom/awscli.pm @@ -412,7 +412,7 @@ sub ec2_get_instances_status { foreach (@{$raw_results->{InstanceStatuses}}) { $instance_results->{$_->{InstanceId}} = { state => $_->{InstanceState}->{Name}, - status => => $_->{InstanceStatus}->{Status} + status => $_->{InstanceStatus}->{Status} }; } @@ -517,8 +517,8 @@ sub ec2_list_resources { AvailabilityZone => $instance->{Placement}->{AvailabilityZone}, InstanceType => $instance->{InstanceType}, State => $instance->{State}->{Name}, - Tags => join(",", @instance_tags), - KeyName => $instance->{KeyName}, + Tags => join(',', @instance_tags), + KeyName => $instance->{KeyName} }; } } @@ -526,6 +526,26 @@ sub ec2_list_resources { return $resource_results; } +sub ec2_get_instances { + my ($self, %options) = @_; + + my $cmd_options = $self->ec2_list_resources_set_cmd(%options); + my $raw_results = $self->execute(cmd_options => $cmd_options); + + my $instance_results = {}; + foreach my $reservation (@{$raw_results->{Reservations}}) { + foreach my $instance (@{$reservation->{Instances}}) { + $instance_results->{ $instance->{InstanceId} } = { + State => $instance->{State}->{Name}, + InstanceType => $instance->{InstanceType}, + LaunchTime => $instance->{LaunchTime} + }; + } + } + + return $instance_results; +} + sub asg_get_resources_set_cmd { my ($self, %options) = @_; diff --git a/cloud/aws/custom/paws.pm b/cloud/aws/custom/paws.pm index 05dfdd5d9..53039be51 100644 --- a/cloud/aws/custom/paws.pm +++ b/cloud/aws/custom/paws.pm @@ -371,6 +371,33 @@ sub ec2_get_instances_status { return $instance_results; } +sub ec2_get_instances { + my ($self, %options) = @_; + + my $instance_results = {}; + eval { + my $ec2 = $self->{paws}->service('EC2', region => $self->{option_results}->{region}); + my $list_instances = $ec2->DescribeInstances(DryRun => 0); + + foreach my $reservation (@{$list_instances->{Reservations}}) { + foreach my $instance (@{$reservation->{Instances}}) { + $instance_results->{ $instance->{InstanceId} } = { + Name => $instance->{InstanceId}, + InstanceType => $instance->{InstanceType}, + State => $instance->{State}->{Name}, + LaunchTime => $instance->{LaunchTime} + }; + } + } + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "error: $@"); + $self->{output}->option_exit(); + } + + return $instance_results; +} + sub ec2spot_get_active_instances { my ($self, %options) = @_; diff --git a/cloud/aws/ec2/mode/instancesstatus.pm b/cloud/aws/ec2/mode/instancesstatus.pm index 4bc5b4651..7433c8053 100644 --- a/cloud/aws/ec2/mode/instancesstatus.pm +++ b/cloud/aws/ec2/mode/instancesstatus.pm @@ -24,121 +24,52 @@ use base qw(centreon::plugins::templates::counter); use strict; use warnings; -use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold); +use POSIX; +use centreon::plugins::misc; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); +use DateTime; + +my $unitdiv = { s => 1, w => 604800, d => 86400, h => 3600, m => 60 }; +my $unitdiv_long = { s => 'seconds', w => 'weeks', d => 'days', h => 'hours', m => 'minutes' }; + +sub custom_uptime_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add( + nlabel => $self->{nlabel} . '.' . $unitdiv_long->{ $self->{instance_mode}->{option_results}->{uptime_unit} }, + unit => $self->{instance_mode}->{option_results}->{uptime_unit}, + instances => $self->{result_values}->{display}, + value => floor($self->{result_values}->{uptime_seconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{uptime_unit} }), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), + min => 0 + ); +} + +sub custom_uptime_threshold { + my ($self, %options) = @_; + + return $self->{perfdata}->threshold_check( + value => floor($self->{result_values}->{uptime_seconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{uptime_unit} }), + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' }, + { label => 'unknown-'. $self->{thlabel}, exit_litteral => 'unknown' } + ] + ); +} + sub custom_status_output { my ($self, %options) = @_; - my $msg = sprintf('state: %s, status: %s', $self->{result_values}->{state}, $self->{result_values}->{status}); - return $msg; -} - -sub custom_status_calc { - my ($self, %options) = @_; - - $self->{result_values}->{state} = $options{new_datas}->{$self->{instance} . '_state'}; - $self->{result_values}->{status} = $options{new_datas}->{$self->{instance} . '_status'}; - $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; - return 0; -} - -sub set_counters { - my ($self, %options) = @_; - - $self->{maps_counters_type} = [ - { name => 'global', type => 0, cb_prefix_output => 'prefix_global_output' }, - { name => 'aws_instances', type => 1, cb_prefix_output => 'prefix_awsinstance_output', - message_multiple => 'All instances are ok' }, - ]; - - $self->{maps_counters}->{global} = [ - { label => 'pending', nlabel => 'ec2.instances.status.pending.count', set => { - key_values => [ { name => 'pending' } ], - output_template => "Pending : %s", - perfdatas => [ - { value => 'pending', template => '%d', min => 0 }, - ], - } - }, - { label => 'running', nlabel => 'ec2.instances.status.running.count', set => { - key_values => [ { name => 'running' } ], - output_template => "Running : %s", - perfdatas => [ - { value => 'running', template => '%d', min => 0 }, - ], - } - }, - { label => 'shuttingdown', nlabel => 'ec2.instances.status.shuttingdown.count', set => { - key_values => [ { name => 'shutting-down' } ], - output_template => "Shutting Down : %s", - perfdatas => [ - { value => 'shutting-down', template => '%d', min => 0 }, - ], - } - }, - { label => 'terminated', nlabel => 'ec2.instances.status.terminated.count', set => { - key_values => [ { name => 'terminated' } ], - output_template => "Terminated : %s", - perfdatas => [ - { value => 'terminated', template => '%d', min => 0 }, - ], - } - }, - { label => 'stopping', nlabel => 'ec2.instances.status.stopping.count', set => { - key_values => [ { name => 'stopping' } ], - output_template => "Stopping : %s", - perfdatas => [ - { value => 'stopping', template => '%d', min => 0 }, - ], - } - }, - { label => 'stopped', nlabel => 'ec2.instances.status.stopped.count', set => { - key_values => [ { name => 'stopped' } ], - output_template => "Stopped : %s", - perfdatas => [ - { value => 'stopped', template => '%d', min => 0 }, - ], - } - }, - ]; - - $self->{maps_counters}->{aws_instances} = [ - { label => 'status', threshold => 0, set => { - key_values => [ { name => 'state' }, { name => 'status' }, { name => 'display' } ], - closure_custom_calc => $self->can('custom_status_calc'), - closure_custom_output => $self->can('custom_status_output'), - closure_custom_perfdata => sub { return 0; }, - closure_custom_threshold_check => \&catalog_status_threshold, - } - }, - ]; -} - -sub new { - my ($class, %options) = @_; - my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); - bless $self, $class; - - $options{options}->add_options(arguments => { - "filter-instanceid:s" => { name => 'filter_instanceid' }, - "warning-status:s" => { name => 'warning_status', default => '' }, - "critical-status:s" => { name => 'critical_status', default => '' } - }); - - return $self; -} - -sub check_options { - my ($self, %options) = @_; - $self->SUPER::check_options(%options); - - $self->change_macros(macros => ['warning_status', 'critical_status']); + return sprintf('state: %s, status: %s', $self->{result_values}->{state}, $self->{result_values}->{status}); } sub prefix_global_output { my ($self, %options) = @_; - return "Total instances "; + return 'Total instances '; } sub prefix_awsinstance_output { @@ -147,29 +78,164 @@ sub prefix_awsinstance_output { return "Instance '" . $options{instance_value}->{display} . "' "; } +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, cb_prefix_output => 'prefix_global_output' }, + { name => 'aws_instances', type => 1, cb_prefix_output => 'prefix_awsinstance_output', + message_multiple => 'All instances are ok', skipped_code => { -10 => 1 } } + ]; + + $self->{maps_counters}->{global} = [ + { label => 'pending', nlabel => 'ec2.instances.status.pending.count', set => { + key_values => [ { name => 'pending' } ], + output_template => "pending: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'running', nlabel => 'ec2.instances.status.running.count', set => { + key_values => [ { name => 'running' } ], + output_template => "running: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'shuttingdown', nlabel => 'ec2.instances.status.shuttingdown.count', set => { + key_values => [ { name => 'shutting-down' } ], + output_template => "shutting down: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'terminated', nlabel => 'ec2.instances.status.terminated.count', set => { + key_values => [ { name => 'terminated' } ], + output_template => "terminated: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'stopping', nlabel => 'ec2.instances.status.stopping.count', set => { + key_values => [ { name => 'stopping' } ], + output_template => "stopping: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + }, + { label => 'stopped', nlabel => 'ec2.instances.status.stopped.count', set => { + key_values => [ { name => 'stopped' } ], + output_template => "stopped: %s", + perfdatas => [ + { template => '%d', min => 0 } + ] + } + } + ]; + + $self->{maps_counters}->{aws_instances} = [ + { label => 'status', type => 2, set => { + key_values => [ { name => 'state' }, { name => 'status' }, { name => 'display' } ], + closure_custom_output => $self->can('custom_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { label => 'uptime', nlabel => 'ec2.uptime', set => { + key_values => [ { name => 'uptime_seconds' }, { name => 'uptime_human' }, { name => 'display' } ], + output_template => 'uptime: %s', + output_use => 'uptime_human', + closure_custom_perfdata => $self->can('custom_uptime_perfdata'), + closure_custom_threshold_check => $self->can('custom_uptime_threshold') + } + } + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'filter-instanceid:s' => { name => 'filter_instanceid' }, + 'add-uptime' => { name => 'add_uptime' }, + 'uptime-unit:s' => { name => 'uptime_unit', default => 'd' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if ($self->{option_results}->{uptime_unit} eq '' || !defined($unitdiv->{$self->{option_results}->{uptime_unit}})) { + $self->{option_results}->{uptime_unit} = 'd'; + } +} + +sub add_uptime { + my ($self, %options) = @_; + + return if (!defined($options{instances}->{ $options{instance_id} })); + + return if ($options{state} !~ /running|stopping/); + + # format: "2021-04-16T07:54:33.000Z" + return if ($options{instances}->{ $options{instance_id} }->{LaunchTime} !~ /^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/); + + my $dt = DateTime->new( + year => $1, + month => $2, + day => $3, + hour => $4, + minute => $5, + second => $6, + time_zone => 'UTC' + ); + $self->{aws_instances}->{ $options{instance_id} }->{uptime_seconds} = time() - $dt->epoch(); + $self->{aws_instances}->{ $options{instance_id} }->{uptime_human} = centreon::plugins::misc::change_seconds( + value => $self->{aws_instances}->{ $options{instance_id} }->{uptime_seconds} + ); +} + sub manage_selection { my ($self, %options) = @_; $self->{global} = { pending => 0, running => 0, 'shutting-down' => 0, terminated => 0, stopping => 0, stopped => 0, }; + $self->{aws_instances} = {}; my $result = $options{custom}->ec2_get_instances_status(); + + my $instances; + if (defined($self->{option_results}->{add_uptime})) { + $instances = $options{custom}->ec2_get_instances(); + } foreach my $instance_id (keys %{$result}) { if (defined($self->{option_results}->{filter_instanceid}) && $self->{option_results}->{filter_instanceid} ne '' && $instance_id !~ /$self->{option_results}->{filter_instanceid}/) { $self->{output}->output_add(long_msg => "skipping '" . $instance_id . "': no matching filter.", debug => 1); next; } - + $self->{aws_instances}->{$instance_id} = { display => $instance_id, state => $result->{$instance_id}->{state}, - status => $result->{$instance_id}->{status}, + status => $result->{$instance_id}->{status} }; - $self->{global}->{$result->{$instance_id}->{state}}++; + $self->add_uptime(instances => $instances, instance_id => $instance_id, state => $result->{$instance_id}->{state}) + if (defined($self->{option_results}->{add_uptime})); + $self->{global}->{ $result->{$instance_id}->{state} }++; } - + if (scalar(keys %{$self->{aws_instances}}) <= 0) { $self->{output}->add_option_msg(short_msg => "No aws instance found."); $self->{output}->option_exit(); @@ -184,7 +250,7 @@ __END__ Check EC2 instances status. -Example: +Example: perl centreon_plugins.pl --plugin=cloud::aws::ec2::plugin --custommode=paws --mode=instances-status --region='eu-west-1' --filter-instanceid='.*' --filter-counters='^running$' --critical-running='10' --verbose @@ -201,21 +267,31 @@ Example: --filter-counters='^running$' Filter by instance id (can be a regexp). +=item B<--add-uptime> + +Monitor instances uptime. + +=item B<--uptime-unit> + +Select the unit for uptime threshold. May be 's' for seconds, 'm' for minutes, +'h' for hours, 'd' for days, 'w' for weeks. Default is days. + =item B<--warning-status> -Set warning threshold for status (Default: ''). +Set warning threshold for status. Can used special variables like: %{state}, %{display} =item B<--critical-status> -Set critical threshold for status (Default: ''). +Set critical threshold for status. Can used special variables like: %{state}, %{display} =item B<--warning-*> B<--critical-*> -Threshold warning. +Thresholds. Can be: 'pending', 'running', 'shuttingdown', -'terminated', 'stopping', 'stopped'. +'terminated', 'stopping', 'stopped', +'uptime'. =back