From add8e81eb4e38acc987ff8e0bf4be2939d90957d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 16 Jan 2019 12:10:13 +0100 Subject: [PATCH] enh spanningtree mode, add listspanningtrees --- snmp_standard/mode/listspanningtrees.pm | 186 ++++++++++++++++++++++++ snmp_standard/mode/spanningtree.pm | 139 +++++++++++------- 2 files changed, 269 insertions(+), 56 deletions(-) create mode 100644 snmp_standard/mode/listspanningtrees.pm diff --git a/snmp_standard/mode/listspanningtrees.pm b/snmp_standard/mode/listspanningtrees.pm new file mode 100644 index 000000000..a763065e4 --- /dev/null +++ b/snmp_standard/mode/listspanningtrees.pm @@ -0,0 +1,186 @@ +# +# Copyright 2019 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 snmp_standard::mode::listspanningtrees; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +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 => + { + "filter-port:s" => { name => 'filter_port' }, + }); + $self->{spanningtrees} = {}; + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +my %mapping_state = ( + 1 => 'disabled', + 2 => 'blocking', + 3 => 'listening', + 4 => 'learning', + 5 => 'forwarding', + 6 => 'broken', + 10 => 'not defined', +); +my %mapping_status = ( + 1 => 'enabled', + 2 => 'disabled', +); + +my $mapping = { + dot1dStpPortState => { oid => '.1.3.6.1.2.1.17.2.15.1.3', map => \%mapping_state }, + dot1dStpPortEnable => { oid => '.1.3.6.1.2.1.17.2.15.1.4', map => \%mapping_status }, +}; +my $oid_dot1dStpPortEntry = '.1.3.6.1.2.1.17.2.15.1'; + +my $oid_dot1dBasePortIfIndex = '.1.3.6.1.2.1.17.1.4.1.2'; + +my %mapping_if_status = ( + 1 => 'up', 2 => 'down', 3 => 'testing', 4 => 'unknown', + 5 => 'dormant', 6 => 'notPresent', 7 => 'lowerLayerDown', +); +my $oid_ifDesc = '.1.3.6.1.2.1.2.2.1.2'; +my $oid_ifAdminStatus = '.1.3.6.1.2.1.2.2.1.7'; +my $oid_ifOpStatus = '.1.3.6.1.2.1.2.2.1.8'; + +sub manage_selection { + my ($self, %options) = @_; + + my $results = $options{snmp}->get_table(oid => $oid_dot1dStpPortEntry, start => $mapping->{dot1dStpPortState}->{oid}, end => $mapping->{dot1dStpPortEnable}->{oid}, nothing_quit => 1); + + my @instances = (); + foreach my $oid (keys %{$results}) { + next if ($oid !~ /^$mapping->{dot1dStpPortState}->{oid}\.(.*)/); + my $instance = $1; + my $map_result = $options{snmp}->map_instance(mapping => $mapping, results => $results, instance => $instance); + + if ($map_result->{dot1dStpPortEnable} =~ /disabled/) { + $self->{output}->output_add(long_msg => sprintf("Skipping interface '%d': Stp port disabled", $instance), debug => 1); + next; + } + push @instances, $instance; + } + + $options{snmp}->load(oids => [ $oid_dot1dBasePortIfIndex ], instances => [ @instances ]); + my $result = $options{snmp}->get_leef(nothing_quit => 1); + + foreach my $oid (keys %{$result}) { + next if ($oid !~ /^$oid_dot1dBasePortIfIndex\./ || !defined($result->{$oid})); + $options{snmp}->load(oids => [ $oid_ifDesc . "." . $result->{$oid}, $oid_ifAdminStatus . "." . $result->{$oid}, $oid_ifOpStatus . "." . $result->{$oid} ]); + } + my $result_if = $options{snmp}->get_leef(); + + foreach my $instance (@instances) { + my $map_result = $options{snmp}->map_instance(mapping => $mapping, results => $results, instance => $instance); + + my $state = (defined($map_result->{dot1dStpPortState})) ? $map_result->{dot1dStpPortState} : 'not defined'; + my $description = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + my $admin_status = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifAdminStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifAdminStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + my $op_status = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifOpStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifOpStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + + if (defined($self->{option_results}->{filter_port}) && $self->{option_results}->{filter_port} ne '' && + $description !~ /$self->{option_results}->{filter_port}/) { + $self->{output}->output_add(long_msg => sprintf("Skipping interface '%s': filtered with options", $description), debug => 1); + next; + } + + $self->{spanningtrees}->{$result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} = { + state => $state, + admin_status => $mapping_if_status{$admin_status}, + op_status => $mapping_if_status{$op_status}, + index => $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}, + description => $description + }; + } +} + +sub run { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $instance (sort keys %{$self->{spanningtrees}}) { + $self->{output}->output_add(long_msg => sprintf("[port = %s] [state = %s] [op_status = %s] [admin_status = %s] [index = %s]", + $self->{spanningtrees}->{$instance}->{description}, $self->{spanningtrees}->{$instance}->{state}, $self->{spanningtrees}->{$instance}->{op_status}, + $self->{spanningtrees}->{$instance}->{admin_status}, $self->{spanningtrees}->{$instance}->{index})); + } + + $self->{output}->output_add(severity => 'OK', + short_msg => 'List ports with Spanning Tree Protocol:'); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); +} + +sub disco_format { + my ($self, %options) = @_; + + $self->{output}->add_disco_format(elements => ['port', 'state', 'op_status', 'admin_status', 'index']); +} + +sub disco_show { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $instance (sort keys %{$self->{spanningtrees}}) { + $self->{output}->add_disco_entry( + port => $self->{spanningtrees}->{$instance}->{description}, + state => $self->{spanningtrees}->{$instance}->{state}, + op_status => $self->{spanningtrees}->{$instance}->{op_status}, + admin_status => $self->{spanningtrees}->{$instance}->{admin_status}, + index => $self->{spanningtrees}->{$instance}->{index} + ); + } +} + +1; + +__END__ + +=head1 MODE + +List ports using Spanning Tree Protocol. + +=over 8 + +=item B<--filter-port> + +Filter by port description (can be a regexp). + +=back + +=cut + diff --git a/snmp_standard/mode/spanningtree.pm b/snmp_standard/mode/spanningtree.pm index f929dad3c..0dff2d4b3 100644 --- a/snmp_standard/mode/spanningtree.pm +++ b/snmp_standard/mode/spanningtree.pm @@ -28,19 +28,21 @@ use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold) sub custom_status_output { my ($self, %options) = @_; - my $msg = "spanning tree state is '" . $self->{result_values}->{state} . "' [index: '" . $self->{result_values}->{index} . "']"; + my $msg = sprintf("spanning tree state is '%s' [op status: '%s'] [admin status: '%s'] [index: '%s']", + $self->{result_values}->{state}, $self->{result_values}->{op_status}, + $self->{result_values}->{admin_status}, $self->{result_values}->{index}); return $msg; } sub custom_status_calc { my ($self, %options) = @_; - $self->{result_values}->{port} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{port} = $options{new_datas}->{$self->{instance} . '_description'}; $self->{result_values}->{state} = $options{new_datas}->{$self->{instance} . '_state'}; + $self->{result_values}->{admin_status} = $options{new_datas}->{$self->{instance} . '_admin_status'}; + $self->{result_values}->{op_status} = $options{new_datas}->{$self->{instance} . '_op_status'}; $self->{result_values}->{index} = $options{new_datas}->{$self->{instance} . '_index'}; - - return 0; } @@ -48,11 +50,12 @@ sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ - { name => 'stp_port', type => 1, cb_prefix_output => 'prefix_peers_output', message_multiple => 'Spanning tree is OK' }, + { name => 'spanningtrees', type => 1, cb_prefix_output => 'prefix_peers_output', message_multiple => 'All spanning trees are ok' }, ]; - $self->{maps_counters}->{stp_port} = [ + $self->{maps_counters}->{spanningtrees} = [ { label => 'status', threshold => 0, set => { - key_values => [ { name => 'state' }, { name => 'index' }, { name => 'display' } ], + key_values => [ { name => 'state' }, { name => 'admin_status' }, { name => 'op_status' }, + { name => 'index' }, { name => 'description' } ], closure_custom_calc => $self->can('custom_status_calc'), closure_custom_output => $self->can('custom_status_output'), closure_custom_perfdata => sub { return 0; }, @@ -65,7 +68,7 @@ sub set_counters { sub prefix_peers_output { my ($self, %options) = @_; - return "Port '" . $options{instance_value}->{display} . "' "; + return "Port '" . $options{instance_value}->{description} . "' "; } sub new { @@ -76,9 +79,9 @@ sub new { $self->{version} = '1.0'; $options{options}->add_options(arguments => { - "filter-port:s" => { name => 'filter_port' }, - "warning-status:s" => { name => 'warning_status', default => '' }, - "critical-status:s" => { name => 'critical_status', default => '%{state} =~ /blocking|broken/' }, + "filter-port:s" => { name => 'filter_port' }, + "warning-status:s" => { name => 'warning_status', default => '' }, + "critical-status:s" => { name => 'critical_status', default => '%{op_status} =~ /up/ && %{state} =~ /blocking|broken/' }, }); return $self; @@ -91,74 +94,94 @@ sub check_options { $self->change_macros(macros => ['warning_status', 'critical_status']); } - -my %states = ( +my %mapping_state = ( 1 => 'disabled', 2 => 'blocking', 3 => 'listening', 4 => 'learning', 5 => 'forwarding', 6 => 'broken', - 10 => 'not defined' + 10 => 'not defined', ); +my %mapping_status = ( + 1 => 'enabled', + 2 => 'disabled', +); + +my $mapping = { + dot1dStpPortState => { oid => '.1.3.6.1.2.1.17.2.15.1.3', map => \%mapping_state }, + dot1dStpPortEnable => { oid => '.1.3.6.1.2.1.17.2.15.1.4', map => \%mapping_status }, +}; +my $oid_dot1dStpPortEntry = '.1.3.6.1.2.1.17.2.15.1'; + +my $oid_dot1dBasePortIfIndex = '.1.3.6.1.2.1.17.1.4.1.2'; + +my %mapping_if_status = ( + 1 => 'up', 2 => 'down', 3 => 'testing', 4 => 'unknown', + 5 => 'dormant', 6 => 'notPresent', 7 => 'lowerLayerDown', +); +my $oid_ifDesc = '.1.3.6.1.2.1.2.2.1.2'; +my $oid_ifAdminStatus = '.1.3.6.1.2.1.2.2.1.7'; +my $oid_ifOpStatus = '.1.3.6.1.2.1.2.2.1.8'; sub manage_selection { my ($self, %options) = @_; - $self->{snmp} = $options{snmp}; - $self->{stp_port} = {}; + + $self->{spanningtrees} = {}; + my $results = $options{snmp}->get_table(oid => $oid_dot1dStpPortEntry, start => $mapping->{dot1dStpPortState}->{oid}, end => $mapping->{dot1dStpPortEnable}->{oid}, nothing_quit => 1); - my $oid_dot1dStpPortEnable = '.1.3.6.1.2.1.17.2.15.1.4'; - my $oid_dot1dStpPortState = '.1.3.6.1.2.1.17.2.15.1.3'; - my $oid_dot1dBasePortIfIndex = '.1.3.6.1.2.1.17.1.4.1.2'; - my $oid_ifDesc = '.1.3.6.1.2.1.2.2.1.2'; - my $results = $self->{snmp}->get_multiple_table(oids => [ - { oid => $oid_dot1dStpPortEnable }, - { oid => $oid_dot1dStpPortState }, - ], nothing_quit => 1); my @instances = (); - foreach my $oid (keys %{$results->{$oid_dot1dStpPortEnable}}) { - $oid =~ /\.([0-9]+)$/; + foreach my $oid (keys %{$results}) { + next if ($oid !~ /^$mapping->{dot1dStpPortState}->{oid}\.(.*)/); my $instance = $1; + my $map_result = $options{snmp}->map_instance(mapping => $mapping, results => $results, instance => $instance); - # '2' => disabled, we skip - if ($results->{$oid_dot1dStpPortEnable}->{$oid} == 2) { - $self->{output}->output_add(long_msg => sprintf("Skipping interface '%d': Stp port disabled", $instance)); + if ($map_result->{dot1dStpPortEnable} =~ /disabled/) { + $self->{output}->output_add(long_msg => sprintf("Skipping interface '%d': Stp port disabled", $instance), debug => 1); next; } - push @instances, $instance; - } - $self->{snmp}->load(oids => [$oid_dot1dBasePortIfIndex], - instances => [@instances]); - my $result = $self->{snmp}->get_leef(nothing_quit => 1); + $options{snmp}->load(oids => [ $oid_dot1dBasePortIfIndex ], instances => [ @instances ]); + my $result = $options{snmp}->get_leef(nothing_quit => 1); - # Get description - foreach my $oid (keys %$result) { + foreach my $oid (keys %{$result}) { next if ($oid !~ /^$oid_dot1dBasePortIfIndex\./ || !defined($result->{$oid})); - - $self->{snmp}->load(oids => [$oid_ifDesc . "." . $result->{$oid}]); + $options{snmp}->load(oids => [ $oid_ifDesc . "." . $result->{$oid}, $oid_ifAdminStatus . "." . $result->{$oid}, $oid_ifOpStatus . "." . $result->{$oid} ]); } - my $result_desc = $self->{snmp}->get_leef(); + my $result_if = $options{snmp}->get_leef(); foreach my $instance (@instances) { - my $stp_state = defined($results->{$oid_dot1dStpPortState}->{$oid_dot1dStpPortState . '.' . $instance}) ? - $results->{$oid_dot1dStpPortState}->{$oid_dot1dStpPortState . '.' . $instance} : 10; - my $descr = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_desc->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? - $result_desc->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + my $map_result = $options{snmp}->map_instance(mapping => $mapping, results => $results, instance => $instance); + my $state = (defined($map_result->{dot1dStpPortState})) ? $map_result->{dot1dStpPortState} : 'not defined'; + my $description = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifDesc . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + my $admin_status = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifAdminStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifAdminStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; + my $op_status = (defined($result->{$oid_dot1dBasePortIfIndex . '.' . $instance}) && defined($result_if->{$oid_ifOpStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}})) ? + $result_if->{$oid_ifOpStatus . '.' . $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} : 'unknown'; if (defined($self->{option_results}->{filter_port}) && $self->{option_results}->{filter_port} ne '' && - $descr !~ /$self->{option_results}->{filter_port}/) { - $self->{output}->output_add(long_msg => sprintf("Skipping interface '%s': filtered with options", $descr)); + $description !~ /$self->{option_results}->{filter_port}/) { + $self->{output}->output_add(long_msg => sprintf("Skipping interface '%s': filtered with options", $description), debug => 1); next; } - $self->{stp_port}->{$descr} = { state => $states{$stp_state}, - index => $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}, - display => $descr }; + + $self->{spanningtrees}->{$result->{$oid_dot1dBasePortIfIndex . '.' . $instance}} = { + state => $state, + admin_status => $mapping_if_status{$admin_status}, + op_status => $mapping_if_status{$op_status}, + index => $result->{$oid_dot1dBasePortIfIndex . '.' . $instance}, + description => $description + }; } + if (scalar(keys %{$self->{spanningtrees}}) <= 0) { + $self->{output}->add_option_msg(short_msg => 'No port with Spanning Tree Protocol found.'); + $self->{output}->option_exit(); + } } 1; @@ -167,21 +190,25 @@ __END__ =head1 MODE -Check Spanning-Tree current state of ports (BRIDGE-MIB). -example: perl centreon_plugins.pl --plugin=network::cisco::standard::snmp::plugin --mode=spanning-tree --hostname=X.X.X.X --snmp-version='2c' --snmp-community='snmpcommunity' --verbose --warning-status='%{state} =~ /forwarding/ && %{port} !~ /^Port/' +Check port Spanning Tree Protocol current state (BRIDGE-MIB). =over 8 +=item B<--filter-port> + +Filter on port description (can be a regexp). + =item B<--warning-status> -Specify logical expression to trigger a warning alert. Can use %{port} and %{state} or %{index} -e.g --warning-status "%{port} eq 'Port-Channel' && %{state} !~ /forwarding/" + +Set warning threshold for status. +Can used special variables like: %{state}, %{op_status}, +%{admin_status}, %{port}, %{index}. =item B<--critical-status> -Specify logical expression to trigger a critical alert. Can use %{port} and %{state} or %{index} -Default is --critical-status='%{state} =~ /blocking|broken/' -=item B<--filter-port> -Filter on port description, can be a regexp +Set critical threshold for status (Default: '%{op_status} =~ /up/ && %{state} =~ /blocking|broken/'). +Can used special variables like: %{state}, %{op_status}, +%{admin_status}, %{port}, %{index}. =back