From 578e6933472d011c8505f93daa37d426dd21b375 Mon Sep 17 00:00:00 2001 From: qgarnier Date: Thu, 14 Dec 2017 11:07:52 +0100 Subject: [PATCH] add mode aws cloudwatch-get-alarms --- cloud/aws/custom/awscli.pm | 66 ++++++-- cloud/aws/custom/paws.pm | 26 ++- cloud/aws/mode/cloudwatchgetalarms.pm | 223 +++++++++++++++++++++++++ cloud/aws/mode/cloudwatchgetmetrics.pm | 2 +- 4 files changed, 305 insertions(+), 12 deletions(-) create mode 100644 cloud/aws/mode/cloudwatchgetalarms.pm diff --git a/cloud/aws/custom/awscli.pm b/cloud/aws/custom/awscli.pm index c74a0cdc4..45e4b6fec 100644 --- a/cloud/aws/custom/awscli.pm +++ b/cloud/aws/custom/awscli.pm @@ -98,11 +98,13 @@ sub cloudwatch_get_metrics_set_cmd { my ($self, %options) = @_; return if (defined($self->{option_results}->{command_options}) && $self->{option_results}->{command_options} ne ''); - $self->{option_results}->{command_options} = + my $cmd_options = "cloudwatch get-metric-statistics --region $options{region} --namespace $options{namespace} --metric-name '$options{metric_name}' --start-time $options{start_time} --end-time $options{end_time} --period $options{period} --statistics " . join(' ', @{$options{statistics}}) . " --output json --dimensions"; foreach my $entry (@{$options{dimensions}}) { - $self->{option_results}->{command_options} .= " 'Name=$entry->{Name},Value=$entry->{Value}'"; - } + $cmd_options .= " 'Name=$entry->{Name},Value=$entry->{Value}'"; + } + + return $cmd_options; } sub cloudwatch_get_metrics { @@ -113,13 +115,15 @@ sub cloudwatch_get_metrics { my $end_time = DateTime->now->iso8601; foreach my $metric_name (@{$options{metrics}}) { - $self->cloudwatch_get_metrics_set_cmd(%options, metric_name => $metric_name, start_time => $start_time, end_time => $end_time); - my ($response) = centreon::plugins::misc::execute(output => $self->{output}, - options => $self->{option_results}, - sudo => $self->{option_results}->{sudo}, - command => $self->{option_results}->{command}, - command_path => $self->{option_results}->{command_path}, - command_options => $self->{option_results}->{command_options}); + my $cmd_options = $self->cloudwatch_get_metrics_set_cmd(%options, metric_name => $metric_name, start_time => $start_time, end_time => $end_time); + my ($response) = centreon::plugins::misc::execute( + output => $self->{output}, + options => $self->{option_results}, + sudo => $self->{option_results}->{sudo}, + command => $self->{option_results}->{command}, + command_path => $self->{option_results}->{command_path}, + command_options => $cmd_options); + my $metric_result; eval { $metric_result = JSON::XS->new->utf8->decode($response); @@ -159,6 +163,48 @@ sub cloudwatch_get_metrics { return $metric_results; } +sub cloudwatch_get_alarms_set_cmd { + my ($self, %options) = @_; + + return if (defined($self->{option_results}->{command_options}) && $self->{option_results}->{command_options} ne ''); + my $cmd_options = "cloudwatch describe-alarms --region $options{region} --output json"; + return $cmd_options; +} + +sub cloudwatch_get_alarms { + my ($self, %options) = @_; + + my $cmd_options = $self->cloudwatch_get_alarms_set_cmd(%options); + my ($response) = centreon::plugins::misc::execute( + output => $self->{output}, + options => $self->{option_results}, + sudo => $self->{option_results}->{sudo}, + command => $self->{option_results}->{command}, + command_path => $self->{option_results}->{command_path}, + command_options => $cmd_options + ); + my $alarm_result; + eval { + $alarm_result = JSON::XS->new->utf8->decode($response); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); + $self->{output}->option_exit(); + } + + my $alarm_results = []; + foreach my $alarm (@{$alarm_result->{MetricAlarms}}) { + push @$alarm_results, { + AlarmName => $alarm->{AlarmName}, + StateValue => $alarm->{StateValue}, + MetricName => $alarm->{MetricName}, + StateReason => $alarm->{StateReason}, + StateUpdatedTimestamp => $alarm->{StateUpdatedTimestamp}, + }; + } + return $alarm_results; +} + 1; __END__ diff --git a/cloud/aws/custom/paws.pm b/cloud/aws/custom/paws.pm index 69e17b4a2..e5d3da182 100644 --- a/cloud/aws/custom/paws.pm +++ b/cloud/aws/custom/paws.pm @@ -94,7 +94,7 @@ sub cloudwatch_get_metrics { my $metric_results = {}; eval { - my $cw = Paws->service('CloudWatch', region => $options{region}); + my $cw = Paws->service('CloudWatch', region => $options{region}); my $start_time = DateTime->now->subtract(seconds => $options{timeframe})->iso8601; my $end_time = DateTime->now->iso8601; @@ -146,6 +146,30 @@ sub cloudwatch_get_metrics { return $metric_results; } +sub cloudwatch_get_alarms { + my ($self, %options) = @_; + + my $alarm_results = []; + eval { + my $cw = Paws->service('CloudWatch', region => $options{region}); + my $alarms = $cw->DescribeAlarms(); + foreach my $alarm (@{$alarms->{MetricAlarms}}) { + push @$alarm_results, { + AlarmName => $alarm->{AlarmName}, + StateValue => $alarm->{StateValue}, + MetricName => $alarm->{MetricName}, + StateReason => $alarm->{StateReason}, + StateUpdatedTimestamp => $alarm->{StateUpdatedTimestamp}, + }; + } + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "error: $@"); + $self->{output}->option_exit(); + } + return $alarm_results; +} + 1; __END__ diff --git a/cloud/aws/mode/cloudwatchgetalarms.pm b/cloud/aws/mode/cloudwatchgetalarms.pm new file mode 100644 index 000000000..c37a83bfa --- /dev/null +++ b/cloud/aws/mode/cloudwatchgetalarms.pm @@ -0,0 +1,223 @@ +# +# Copyright 2017 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package cloud::aws::mode::cloudwatchgetalarms; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::misc; +use centreon::plugins::statefile; + +my $instance_mode; + +sub custom_status_threshold { + my ($self, %options) = @_; + my $status = 'ok'; + my $message; + + eval { + local $SIG{__WARN__} = sub { $message = $_[0]; }; + local $SIG{__DIE__} = sub { $message = $_[0]; }; + + if (defined($instance_mode->{option_results}->{critical_status}) && $instance_mode->{option_results}->{critical_status} ne '' && + eval "$instance_mode->{option_results}->{critical_status}") { + $status = 'critical'; + } elsif (defined($instance_mode->{option_results}->{warning_status}) && $instance_mode->{option_results}->{warning_status} ne '' && + eval "$instance_mode->{option_results}->{warning_status}") { + $status = 'warning'; + } + }; + if (defined($message)) { + $self->{output}->output_add(long_msg => 'filter status issue: ' . $message); + } + + return $status; +} + +sub custom_status_output { + my ($self, %options) = @_; + + my $msg = sprintf("alarm [name: %s] [state: %s] [metric: %s] [reason: %s] %s", $self->{result_values}->{alarm_name}, + $self->{result_values}->{state_value}, $self->{result_values}->{metric_name}, + $self->{result_values}->{state_reason}, centreon::plugins::misc::change_seconds(value => $self->{result_values}->{last_update})); + return $msg; +} + +sub custom_status_calc { + my ($self, %options) = @_; + + $self->{result_values}->{alarm_name} = $options{new_datas}->{$self->{instance} . '_AlarmName'}; + $self->{result_values}->{state_value} = $options{new_datas}->{$self->{instance} . '_StateValue'}; + $self->{result_values}->{metric_name} = $options{new_datas}->{$self->{instance} . '_MetricName'}; + $self->{result_values}->{last_update} = $options{new_datas}->{$self->{instance} . '_LastUpdate'}; + $self->{result_values}->{state_reason} = $options{new_datas}->{$self->{instance} . '_StateReason'}; + return 0; +} + + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'alarms', type => 2, message_multiple => '0 problem(s) detected', display_counter_problem => { label => 'alerts', min => 0 }, + group => [ { name => 'alarm', skipped_code => { -11 => 1 } } ] + } + ]; + + $self->{maps_counters}->{alarm} = [ + { label => 'status', threshold => 0, set => { + key_values => [ { name => 'AlarmName' }, { name => 'StateValue' }, { name => 'MetricName' }, { name => 'StateReason' }, { name => 'LastUpdate' } ], + 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 => $self->can('custom_status_threshold'), + } + }, + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + "region:s" => { name => 'region' }, + "filter-alarm-name:s" => { name => 'filter_alarm_name' }, + "warning-status:s" => { name => 'warning_status', default => '%{state_value} =~ /INSUFFICIENT_DATA/i' }, + "critical-status:s" => { name => 'critical_status', default => '%{state_value} =~ /ALARM/i' }, + "memory" => { name => 'memory' }, + }); + + centreon::plugins::misc::mymodule_load(output => $self->{output}, module => 'Date::Parse', + error_msg => "Cannot load module 'Date::Parse'."); + $self->{statefile_cache} = centreon::plugins::statefile->new(%options); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if (!defined($self->{option_results}->{region}) || $self->{option_results}->{region} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --region option."); + $self->{output}->option_exit(); + } + $instance_mode = $self; + $self->change_macros(); + + if (defined($self->{option_results}->{memory})) { + $self->{statefile_cache}->check_options(%options); + } +} + +sub change_macros { + my ($self, %options) = @_; + + foreach (('warning_status', 'critical_status')) { + if (defined($self->{option_results}->{$_})) { + $self->{option_results}->{$_} =~ s/%\{(.*?)\}/\$self->{result_values}->{$1}/g; + } + } +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{alarms}->{global} = { alarm => {} }; + my $alarm_results = $options{custom}->cloudwatch_get_alarms( + region => $self->{option_results}->{region}, + ); + + my $last_time; + if (defined($self->{option_results}->{memory})) { + $self->{statefile_cache}->read(statefile => 'cache_aws_' . $self->{mode} . '_' . $self->{option_results}->{region}); + $last_time = $self->{statefile_cache}->get(name => 'last_time'); + } + + my ($i, $current_time) = (1, time()); + foreach my $alarm (@{$alarm_results}) { + my $create_time = Date::Parse::str2time($alarm->{StateUpdatedTimestamp}); + if (!defined($create_time)) { + $self->{manager}->{output}->output_add(severity => 'UNKNOWN', + short_msg => "Can't Parse date '" . $alarm->{StateUpdatedTimestamp} . "'"); + next; + } + + next if (defined($self->{option_results}->{memory}) && defined($last_time) && $last_time > $create_time); + if (defined($self->{option_results}->{filter_alarm_name}) && $self->{option_results}->{filter_alarm_name} ne '' && + $alarm->{AlarmName} !~ /$self->{option_results}->{filter_alarm_name}/) { + $self->{output}->output_add(long_msg => "skipping '" . $alarm->{AlarmName} . "': no matching filter.", debug => 1); + next; + } + + my $diff_time = $current_time - $create_time; + + $self->{alarms}->{global}->{alarm}->{$i} = { + %$alarm, + LastUpdate => $diff_time, + }; + $i++; + } + + if (defined($self->{option_results}->{memory})) { + $self->{statefile_cache}->write(data => { last_time => $current_time }); + } +} + +1; + +__END__ + +=head1 MODE + +Check cloudwatch alarms. + +=over 8 + +=item B<--region> + +Set the region name (Required). + +=item B<--filter-alarm-name> + +Filter by alarm name (can be a regexp). + +=item B<--warning-status> + +Set warning threshold for status (Default: '%{state_value} =~ /INSUFFICIENT_DATA/i') +Can used special variables like: %{alarm_name}, %{state_value}, %{metric_name}, %{last_update} + +=item B<--critical-status> + +Set critical threshold for status (Default: '%{state_value} =~ /ALARM/i'). +Can used special variables like: %{alarm_name}, %{state_value}, %{metric_name}, %{last_update} + +=item B<--memory> + +Only check new alarms. + +=back + +=cut diff --git a/cloud/aws/mode/cloudwatchgetmetrics.pm b/cloud/aws/mode/cloudwatchgetmetrics.pm index c0d16faa4..de9908b48 100644 --- a/cloud/aws/mode/cloudwatchgetmetrics.pm +++ b/cloud/aws/mode/cloudwatchgetmetrics.pm @@ -106,7 +106,7 @@ sub check_options { my ($self, %options) = @_; $self->SUPER::check_options(%options); - if (!defined($self->{option_results}->{region}) || $self->{option_results}->{namespace} eq '') { + if (!defined($self->{option_results}->{region}) || $self->{option_results}->{region} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify --region option."); $self->{output}->option_exit(); }