From 4d30d577f21e2b37d9205c6408be798ee406eeab Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 22 Feb 2019 11:19:09 +0100 Subject: [PATCH] WIP: bind9 statistics channel plugin --- apps/bind9/web/custom/api.pm | 371 +++++++++++++++++++++++++++++ apps/bind9/web/mode/memoryusage.pm | 130 ++++++++++ apps/bind9/web/mode/serverusage.pm | 128 ++++++++++ apps/bind9/web/plugin.pm | 50 ++++ 4 files changed, 679 insertions(+) create mode 100644 apps/bind9/web/custom/api.pm create mode 100644 apps/bind9/web/mode/memoryusage.pm create mode 100644 apps/bind9/web/mode/serverusage.pm create mode 100644 apps/bind9/web/plugin.pm diff --git a/apps/bind9/web/custom/api.pm b/apps/bind9/web/custom/api.pm new file mode 100644 index 000000000..28ccd3e7e --- /dev/null +++ b/apps/bind9/web/custom/api.pm @@ -0,0 +1,371 @@ +# +# 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 apps::bind9::web::custom::api; + +use strict; +use warnings; +use centreon::plugins::misc; +use centreon::plugins::http; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (!defined($options{output})) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3; + } + if (!defined($options{options})) { + $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); + $options{output}->option_exit(); + } + + if (!defined($options{noptions})) { + $options{options}->add_options(arguments => { + "hostname:s" => { name => 'hostname' }, + "port:s" => { name => 'port' }, + "proto:s" => { name => 'proto' }, + "url-path:s" => { name => 'url_path' }, + "proxyurl:s" => { name => 'proxyurl' }, + "timeout:s" => { name => 'timeout' }, + "ssl-opt:s@" => { name => 'ssl_opt' }, + "unknown-status:s" => { name => 'unknown_status', default => '%{http_code} < 200 or %{http_code} >= 300' }, + "warning-status:s" => { name => 'warning_status' }, + "critical-status:s" => { name => 'critical_status' }, + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{mode} = $options{mode}; + $self->{http} = centreon::plugins::http->new(output => $self->{output}); + + return $self; + +} + +# Method to manage multiples +sub set_options { + my ($self, %options) = @_; + # options{options_result} + + $self->{option_results} = $options{option_results}; +} + +# Method to manage multiples +sub set_defaults { + my ($self, %options) = @_; + # options{default} + + # Manage default value + foreach (keys %{$options{default}}) { + if ($_ eq $self->{mode}) { + for (my $i = 0; $i < scalar(@{$options{default}->{$_}}); $i++) { + foreach my $opt (keys %{$options{default}->{$_}[$i]}) { + if (!defined($self->{option_results}->{$opt}[$i])) { + $self->{option_results}->{$opt}[$i] = $options{default}->{$_}[$i]->{$opt}; + } + } + } + } + } +} + +sub check_options { + my ($self, %options) = @_; + + $self->{hostname} = (defined($self->{option_results}->{hostname})) ? $self->{option_results}->{hostname} : undef; + $self->{port} = (defined($self->{option_results}->{port})) ? $self->{option_results}->{port} : 8080; + $self->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'http'; + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 10; + $self->{proxyurl} = (defined($self->{option_results}->{proxyurl})) ? $self->{option_results}->{proxyurl} : undef; + $self->{url_path} = (defined($self->{option_results}->{url_path})) ? $self->{option_results}->{url_path} : '/'; + $self->{unknown_status} = (defined($self->{option_results}->{unknown_status})) ? $self->{option_results}->{unknown_status} : undef; + $self->{warning_status} = (defined($self->{option_results}->{warning_status})) ? $self->{option_results}->{warning_status} : undef; + $self->{critical_status} = (defined($self->{option_results}->{critical_status})) ? $self->{option_results}->{critical_status} : undef; + + if (!defined($self->{hostname})) { + $self->{output}->add_option_msg(short_msg => "Need to specify hostname option."); + $self->{output}->option_exit(); + } + + return 0; +} + +sub get_uniq_id { + my ($self, %options) = @_; + + return $self->{hostname} . '_' . $self->{port}; +} + +sub build_options_for_httplib { + my ($self, %options) = @_; + + $self->{option_results}->{hostname} = $self->{hostname}; + $self->{option_results}->{timeout} = $self->{timeout}; + $self->{option_results}->{port} = $self->{port}; + $self->{option_results}->{proto} = $self->{proto}; + $self->{option_results}->{proxyurl} = $self->{proxyurl}; + $self->{option_results}->{url_path} = $self->{url_path}; + $self->{option_results}->{unknown_status} = $self->{unknown_status}; + $self->{option_results}->{warning_status} = $self->{warning_status}; + $self->{option_results}->{critical_status} = $self->{critical_status}; +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub load_response { + my ($self, %options) = @_; + + if ($self->{response_type} eq 'xml') { + centreon::plugins::misc::mymodule_load(output => $self->{output}, module => 'XML::XPath', + error_msg => "Cannot load module 'XML::XPath'."); + eval { + $self->{xpath_response} = XML::XPath->new(xml => $options{response}); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot load XML response"); + $self->{output}->option_exit(); + } + } +} + +sub request { + my ($self, %options) = @_; + + $self->settings(); + my $response = $self->{http}->request(); + + my $headers = $self->{http}->get_header(); + my $content_type = $headers->header('Content-Type'); + if (!defined($content_type) || $content_type !~ /(xml|json)/i) { + $self->{output}->add_option_msg(short_msg => "content-type not set"); + $self->{output}->option_exit(); + } + + $self->{response_type} = $1; + if ($self->{response_type} eq 'json') { + $self->{output}->add_option_msg(short_msg => "json format unsupported"); + $self->{output}->option_exit(); + } + + $self->load_response(response => $response); + my $method = $self->can("get_api_version_$self->{response_type}"); + if (!defined($method)) { + $self->{output}->add_option_msg(short_msg => "method 'get_api_version_$self->{response_type}' unsupported"); + $self->{output}->option_exit(); + } + $self->$method(); + if (!defined($self->{api_version}) || $self->{api_version} !~ /^(\d+)/) { + $self->{output}->add_option_msg(short_msg => "cannot get api version"); + $self->{output}->option_exit(); + } + + $self->{api_version} = $1; +} + +sub get_api_version_xml { + my ($self, %options) = @_; + + eval { + my $nodesets = $self->{xpath_response}->find('/statistics/@version'); + my $node = $nodesets->get_node(1); + $self->{api_version} = $node->getNodeValue(); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot lookup: $@"); + $self->{output}->option_exit(); + } +} + +sub load_memory_xml_v3 { + my ($self, %options) = @_; + + my $memory = {}; + + my $nodesets = $self->{xpath_response}->find('//memory//TotalUse'); + my $node = $nodesets->get_node(1); + return $memory if (!defined($node)); + $memory->{total_use} = $node->string_value; + + $nodesets = $self->{xpath_response}->find('//memory//InUse'); + $node = $nodesets->get_node(1); + return $memory if (!defined($node)); + $memory->{in_use} = $node->string_value; + + return $memory; +} + +sub load_zones_xml_v3 { + my ($self, %options) = @_; + + my $zones = {}; + my $nodesets = $self->{xpath_response}->find('//views//zones/zone'); + foreach my $node ($nodesets->get_nodelist()) { + my $name = $node->getAttribute('name'); + next if (!defined($name)); + $zones->{$name} = { counters => { rcode => {}, qtype => {} } }; + foreach my $counters_node ($node->getChildNodes()) { + next if ($counters_node->getLocalName() ne 'counters'); + my $type = $counters_node->getAttribute('type'); + foreach my $counter_node ($counters_node->getChildNodes()) { + my $counter_name = $counter_node->getAttribute('name'); + $zones->{$name}->{counters}->{$type}->{$counter_name} = $counter_node->string_value; + } + } + } + + return $zones; +} + +sub load_server_xml_v3 { + my ($self, %options) = @_; + + my $server = { counters => { } }; + my $nodesets = $self->{xpath_response}->find('//server//counters'); + foreach my $node ($nodesets->get_nodelist()) { + my $type = $node->getAttribute('type'); + next if (!defined($type)); + foreach my $counter_node ($node->getChildNodes()) { + my $counter_name = $counter_node->getAttribute('name'); + $server->{counters}->{$type} = {} + if (!defined($server->{counters}->{$type})); + $server->{counters}->{$type}->{$counter_name} = $counter_node->string_value; + } + } + + return $server; +} + +sub get_memory { + my ($self, %options) = @_; + + $self->request(); + my $method = $self->can("load_memory_$self->{response_type}_v$self->{api_version}"); + if (!defined($method)) { + $self->{output}->add_option_msg(short_msg => "method 'load_memory_$self->{response_type}_v$self->{api_version}' unsupported"); + $self->{output}->option_exit(); + } + + my $memory = $self->$method(); + if (!defined($memory->{in_use})) { + $self->{output}->add_option_msg(short_msg => "cannot find memory information"); + $self->{output}->option_exit(); + } + + return $memory; +} + +sub get_zones { + my ($self, %options) = @_; + + $self->request(); + my $method = $self->can("load_zones_$self->{response_type}_v$self->{api_version}"); + if (!defined($method)) { + $self->{output}->add_option_msg(short_msg => "method 'load_zones_$self->{response_type}_v$self->{api_version}' unsupported"); + $self->{output}->option_exit(); + } + + my $zones = $self->$method(); + if (scalar(keys %{$zones}) == 0) { + $self->{output}->add_option_msg(short_msg => "cannot find zones information"); + $self->{output}->option_exit(); + } + + return $zones; +} + +sub get_server { + my ($self, %options) = @_; + + $self->request(); + my $method = $self->can("load_server_$self->{response_type}_v$self->{api_version}"); + if (!defined($method)) { + $self->{output}->add_option_msg(short_msg => "method 'load_server_$self->{response_type}_v$self->{api_version}' unsupported"); + $self->{output}->option_exit(); + } + + my $server = $self->$method(); + if (scalar(keys %{$server->{counters}}) == 0) { + $self->{output}->add_option_msg(short_msg => "cannot find server information"); + $self->{output}->option_exit(); + } + + return $server; +} + +1; + +__END__ + +=head1 NAME + +Statistics Channels API + +=head1 SYNOPSIS + +Statistics Channels API custom mode + +=head1 API OPTIONS + +=over 8 + +=item B<--hostname> + +Statistics Channels hostname. + +=item B<--port> + +Port used (Default: 8080) + +=item B<--proto> + +Specify https if needed (Default: 'http') + +=item B<--url-path> + +Statistics Channel API Path (Default: '/'). + +=item B<--proxyurl> + +Proxy URL if any + +=item B<--timeout> + +Set HTTP timeout + +=item B<--ssl-opt> + +Set SSL Options (--ssl-opt="SSL_version => TLSv1" --ssl-opt="SSL_verify_mode => SSL_VERIFY_NONE"). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/apps/bind9/web/mode/memoryusage.pm b/apps/bind9/web/mode/memoryusage.pm new file mode 100644 index 000000000..eda534b51 --- /dev/null +++ b/apps/bind9/web/mode/memoryusage.pm @@ -0,0 +1,130 @@ +# +# 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 apps::bind9::web::mode::memoryusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; + +sub custom_usage_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add(label => 'used', unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_usage_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_usage_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Memory Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_usage_calc { + my ($self, %options) = @_; + + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_used'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_free} = 100 - $self->{result_values}->{prct_used}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + + return 0; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'memory', type => 0 } + ]; + + $self->{maps_counters}->{memory} = [ + { label => 'usage', set => { + key_values => [ { name => 'used' }, { name => 'total' } ], + closure_custom_calc => $self->can('custom_usage_calc'), + closure_custom_output => $self->can('custom_usage_output'), + closure_custom_perfdata => $self->can('custom_usage_perfdata'), + closure_custom_threshold_check => $self->can('custom_usage_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 => { + }); + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->get_memory(); + $self->{memory} = { used => $result->{in_use}, total => $result->{total_use} }; +} + +1; + +__END__ + +=head1 MODE + +Check bind memory usage. + +=over 8 + +=item B<--warning-usage> + +Threshold warning. + +=item B<--critical-usage> + +Threshold critical. + +=back + +=cut diff --git a/apps/bind9/web/mode/serverusage.pm b/apps/bind9/web/mode/serverusage.pm new file mode 100644 index 000000000..9dfe87b83 --- /dev/null +++ b/apps/bind9/web/mode/serverusage.pm @@ -0,0 +1,128 @@ +# +# 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 apps::bind9::web::mode::serverusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'server', type => 0, skipped_code => { -10 => 1, 11 => -1 } } + ]; + + $self->{maps_counters}->{server} = []; + + my @map = ( + ['opcode_query', 'opcode query : %s', 'opcode-query'], + ['opcode_iquery', 'opcode iquery : %s', 'opcode-iquery'], + ['opcode_status', 'opcode status : %s', 'opcode-status'], + ['opcode_notify', 'opcode notify : %s', 'opcode-notify'], + ['opcode_update', 'opcode update : %s', 'opcode-update'], + ['qtype_a', 'qtype A : %s', 'qtype-a'], + ['qtype_cname', 'qtype CNAME : %s', 'qtype-cname'], + ['qtype_mx', 'qtype MX : %s', 'qtype-mx'], + ['qtype_txt', 'qtype TXT : %s', 'qtype-txt'], + ['qtype_soa', 'qtype SOA : %s', 'qtype-soa'], + ['qtype_ptr', 'qtype PTR : %s', 'qtype-ptr'], + ['qtype_ns', 'qtype NS : %s', 'qtype-ns'], + ['nsstat_requestv4', 'nsstat request v4 : %s', 'nsstat-requestv4'], + ['nsstat_requestv6', 'nsstat request v6 : %s', 'nsstat-requestv6'], + ); + + for (my $i = 0; $i < scalar(@map); $i++) { + my $perf_label = $map[$i]->[2]; + $perf_label =~ s/-/_/g; + push @{$self->{maps_counters}->{server}}, { label => $map[$i]->[2], display_ok => 0, set => { + key_values => [ { name => $map[$i]->[0], diff => 1 } ], + output_template => $map[$i]->[1], + perfdatas => [ + { label => $perf_label, value => $map[$i]->[0] . '_absolute', template => '%s', min => 0 }, + ], + } + }; + } +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => { + }); + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->get_server(); + $self->{server} = { }; + + # Not present in response if no request on the server + foreach ('a', 'cname', 'mx', 'txt', 'soa', 'ptr', 'ns', 'any') { + $self->{server}->{'qtype_' . $_} = 0; + } + + $self->{output}->output_add(severity => 'OK', + short_msg => 'All bind9 counters are ok'); + foreach my $type (keys %{$result->{counters}}) { + foreach my $counter (keys %{$result->{counters}->{$type}}) { + $self->{server}->{lc($type) . '_' . lc($counter)} = $result->{counters}->{$type}->{$counter}; + } + } + + $self->{cache_name} = "bind9_" . $self->{mode} . '_' . $options{custom}->get_uniq_id() . '_' . + (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')); +} + +1; + +__END__ + +=head1 MODE + +Check bind global server usage. + +=over 8 + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='request' + +=item B<--warning-*> <--critical-*> + +Thresholds. +Can be: 'opcode-query', 'opcode-iquery', 'opcode-status', 'opcode-notify', 'opcode-update', +'qtype-a', 'qtype-cname', 'qtype-mx', 'qtype-txt', 'qtype-soa', 'qtype-ptr', 'qtype-ns', 'qtype-any', +'nsstat-requestv4', 'nsstat-requestv6'. + +=back + +=cut diff --git a/apps/bind9/web/plugin.pm b/apps/bind9/web/plugin.pm new file mode 100644 index 000000000..11a4e0111 --- /dev/null +++ b/apps/bind9/web/plugin.pm @@ -0,0 +1,50 @@ +# +# 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 apps::bind9::web::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_custom); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + %{$self->{modes}} = ( + 'memory-usage' => 'apps::bind9::web::mode::memoryusage', + 'server-usage' => 'apps::bind9::web::mode::serverusage', + ); + + $self->{custom_modes}{api} = 'apps::bind9::web::custom::api'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Bind9 server through HTTP statistics channels. + +=cut