(plugin) apps::protocols::snmp - add snmp cache system (#4443)

This commit is contained in:
qgarnier 2023-06-06 10:26:48 +02:00 committed by GitHub
parent 4f8e3a2ea9
commit 1b79704575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 245 additions and 4 deletions

View File

@ -0,0 +1,115 @@
#
# Copyright 2023 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 apps::protocols::snmp::mode::cache;
use base qw(centreon::plugins::mode);
use strict;
use warnings;
use JSON::XS;
sub new {
my ($class, %options) = @_;
my $self = $class->SUPER::new(package => __PACKAGE__, %options);
bless $self, $class;
$options{options}->add_options(arguments => {
'file:s' => { name => 'file' },
'snmpwalk:s@' => { name => 'snmpwalk' },
'snmpget:s@' => { name => 'snmpget' }
});
return $self;
}
sub check_options {
my ($self, %options) = @_;
$self->SUPER::init(%options);
if (!defined($self->{option_results}->{file}) || $self->{option_results}->{file} eq '') {
$self->{output}->add_option_msg(short_msg => "Missing parameter --file");
$self->{output}->option_exit();
}
}
sub run {
my ($self, %options) = @_;
my $snmp_datas = {};
if (defined($self->{option_results}->{snmpwalk})) {
foreach my $oid (@{$self->{option_results}->{snmpwalk}}) {
my $result = $options{snmp}->get_table(oid => $oid);
$snmp_datas = { %$snmp_datas, %$result };
}
}
if (defined($self->{option_results}->{snmpget})) {
my $result = $options{snmp}->get_leef(oids => $self->{option_results}->{snmpget});
$snmp_datas = { %$snmp_datas, %$result };
}
my $json;
eval {
$json = JSON::XS->new->encode($snmp_datas);
};
my $fh;
if (!open($fh, '>', $self->{option_results}->{file})) {
$self->{output}->add_option_msg(short_msg => "Can't open file '$self->{option_results}->{file}': $!");
$self->{output}->option_exit();
}
print $fh $json;
close($fh);
$self->{output}->output_add(
severity => 'OK',
short_msg => 'SNMP cache file created'
);
$self->{output}->display(force_ignore_perfdata => 1);
$self->{output}->exit();
}
1;
__END__
=head1 MODE
Cache SNMP datas in a JSON cache file.
=over 8
=item B<--file>
JSON cache file path.
=item B<--snmpget>
Retrieve a management value.
=item B<--snmpwalk>
Retrieve a subtree of management values.
=back
=cut

View File

@ -30,6 +30,7 @@ sub new {
bless $self, $class;
$self->{modes} = {
'cache' => 'apps::protocols::snmp::mode::cache',
'collection' => 'apps::protocols::snmp::mode::collection',
'discovery' => 'snmp_standard::mode::discovery',
'dynamic-command' => 'snmp_standard::mode::dynamiccommand',

View File

@ -22,6 +22,7 @@ package centreon::plugins::snmp;
use strict;
use warnings;
use centreon::plugins::misc;
use SNMP;
use Socket;
use POSIX;
@ -54,6 +55,7 @@ sub new {
'maxrepetitions:s' => { name => 'maxrepetitions', default => 50 },
'subsetleef:s' => { name => 'subsetleef', default => 50 },
'subsettable:s' => { name => 'subsettable', default => 100 },
'snmp-cache-file:s' => { name => 'snmp_cache_file' },
'snmp-autoreduce:s' => { name => 'snmp_autoreduce' },
'snmp-force-getnext' => { name => 'snmp_force_getnext' },
'snmp-username:s' => { name => 'snmp_security_name' },
@ -221,6 +223,24 @@ sub autoreduce_leef {
return 0;
}
sub get_leef_cache {
my ($self, %options) = @_;
my $results = {};
foreach my $oid (@{$options{oids}}) {
if (defined($self->{snmp_cache}->{$oid})) {
$results->{$oid} = $self->{snmp_cache}->{$oid};
}
}
if ($options{nothing_quit} == 1 && scalar(keys %$results) <= 0) {
$self->{output}->add_option_msg(short_msg => 'SNMP GET Request: Cant get a single value.');
$self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit});
}
return $results;
}
sub get_leef {
my ($self, %options) = @_;
# $options{dont_quit} = integer
@ -247,6 +267,10 @@ sub get_leef {
@{$self->{oids_loaded}} = ();
}
if ($self->{use_snmp_cache} == 1) {
return $self->get_leef_cache(oids => $options{oids}, nothing_quit => $nothing_quit);
}
my $results = {};
$self->{array_ref_ar} = [];
my $subset_current = 0;
@ -396,6 +420,43 @@ sub multiple_find_bigger {
return $getting->{pop(@values)};
}
sub get_multiple_table_cache {
my ($self, %options) = @_;
my $results = {};
foreach my $entry (@{$options{oids}}) {
my $result = $self->get_table_cache(
oid => $entry->{oid},
start => $entry->{start},
end => $entry->{end},
nothing_quit => 0
);
if ($options{return_type} == 0) {
$results->{ $entry->{oid} } = $result;
} else {
$results = { %$results, %$result };
}
}
my $total = 0;
if ($options{nothing_quit} == 1) {
if ($options{return_type} == 1) {
$total = scalar(keys %$results);
} else {
foreach (keys %$results) {
$total += scalar(keys %{$results->{$_}});
}
}
if ($total == 0) {
$self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.');
$self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit});
}
}
return $results;
}
sub get_multiple_table {
my ($self, %options) = @_;
# $options{dont_quit} = integer
@ -408,6 +469,14 @@ sub get_multiple_table {
my ($nothing_quit) = (defined($options{nothing_quit}) && $options{nothing_quit} == 1) ? 1 : 0;
$self->set_error();
if ($self->{use_snmp_cache} == 1) {
return $self->get_multiple_table_cache(
oids => $options{oids},
return_type => $return_type,
nothing_quit => $nothing_quit
);
}
my $working_oids = {};
my $results = {};
# Check overlap
@ -429,7 +498,7 @@ sub get_multiple_table {
}
if ($return_type == 0) {
$results->{$entry->{oid}} = {};
$results->{ $entry->{oid} } = {};
}
}
@ -560,6 +629,29 @@ sub get_multiple_table {
return $results;
}
sub get_table_cache {
my ($self, %options) = @_;
my $branch = defined($options{start}) ? $options{start} : $options{oid};
my $results = {};
foreach my $oid ($self->oid_lex_sort(keys %{$self->{snmp_cache}})) {
if ($oid =~ /^$branch\./) {
$results->{$oid} = $self->{snmp_cache}->{$oid};
if (defined($options{end}) && $self->check_oid_up(current => $oid, end => $options{end})) {
last;
}
}
}
if ($options{nothing_quit} == 1 && scalar(keys %$results) <= 0) {
$self->{output}->add_option_msg(short_msg => 'SNMP Table Request: Cant get a single value.');
$self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit});
}
return $results;
}
sub get_table {
my ($self, %options) = @_;
# $options{dont_quit} = integer
@ -578,6 +670,15 @@ sub get_table {
$options{end} = $self->clean_oid($options{end});
}
if ($self->{use_snmp_cache} == 1) {
return $self->get_table_cache(
oid => $options{oid},
start => $options{start},
end => $options{end},
nothing_quit => $nothing_quit
);
}
# we use a medium (UDP have a PDU limit. SNMP protcol cant send multiples for one request)
# So we need to manage
# It's for "bulk". We ask 50 next values. If you set 1, it's like a getnext (snmp v1)
@ -597,7 +698,7 @@ sub get_table {
$self->{output}->option_exit(exit_litteral => $self->{snmp_errors_exit});
}
my $main_indice = $1 . "." . $2;
my $main_indice = $1 . '.' . $2;
my $results = {};
# Quit if base not the same or 'ENDOFMIBVIEW' value
@ -751,7 +852,28 @@ sub check_oid_up {
sub check_options {
my ($self, %options) = @_;
# $options{option_results} = ref to options result
$self->{snmp_errors_exit} = $options{option_results}->{snmp_errors_exit};
$self->{use_snmp_cache} = 0;
if (defined($options{option_results}->{snmp_cache_file}) && $options{option_results}->{snmp_cache_file} ne '') {
centreon::plugins::misc::mymodule_load(
output => $self->{output},
module => 'JSON::XS',
error_msg => "Cannot load module 'JSON::XS'."
);
my $content = centreon::plugins::misc::slurp_file(output => $self->{output}, file => $options{option_results}->{snmp_cache_file});
eval {
$self->{snmp_cache} = JSON::XS->new->decode($content);
};
if ($@) {
$self->{output}->add_option_msg(short_msg => "Cannot decode json cache file: $@");
$self->{output}->option_exit();
}
$self->{use_snmp_cache} = 1;
return ;
}
if (!defined($options{option_results}->{host})) {
$self->{output}->add_option_msg(short_msg => 'Missing parameter --hostname.');
@ -768,7 +890,6 @@ sub check_options {
$self->{maxrepetitions} = $options{option_results}->{maxrepetitions};
$self->{subsetleef} = (defined($options{option_results}->{subsetleef}) && $options{option_results}->{subsetleef} =~ /^[0-9]+$/) ? $options{option_results}->{subsetleef} : 50;
$self->{subsettable} = (defined($options{option_results}->{subsettable}) && $options{option_results}->{subsettable} =~ /^[0-9]+$/) ? $options{option_results}->{subsettable} : 100;
$self->{snmp_errors_exit} = $options{option_results}->{snmp_errors_exit};
$self->{snmp_autoreduce} = 0;
$self->{snmp_autoreduce_divisor} = 2;
if (defined($options{option_results}->{snmp_autoreduce})) {
@ -1023,6 +1144,10 @@ Auto reduce SNMP request size in case of SNMP errors (By default, the divisor is
Use snmp getnext function (even in snmp v2c and v3).
=item B<--snmp-cache-file>
Use SNMP cache file.
=item B<--snmp-username>
Security name (only for SNMP v3).