From 05777c9e4dfc1d5ef4c4e67f200e1559222eebfe Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 7 Jul 2020 11:25:34 +0200 Subject: [PATCH] wip: cisco ssms rest api --- network/cisco/ssms/restapi/custom/api.pm | 275 ++++++++++++++++++++ network/cisco/ssms/restapi/mode/licenses.pm | 226 ++++++++++++++++ network/cisco/ssms/restapi/plugin.pm | 49 ++++ 3 files changed, 550 insertions(+) create mode 100644 network/cisco/ssms/restapi/custom/api.pm create mode 100644 network/cisco/ssms/restapi/mode/licenses.pm create mode 100644 network/cisco/ssms/restapi/plugin.pm diff --git a/network/cisco/ssms/restapi/custom/api.pm b/network/cisco/ssms/restapi/custom/api.pm new file mode 100644 index 000000000..dcb508dfb --- /dev/null +++ b/network/cisco/ssms/restapi/custom/api.pm @@ -0,0 +1,275 @@ +# +# Copyright 2020 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 network::cisco::ssms::restapi::custom::api; + +use strict; +use warnings; +use centreon::plugins::http; +use JSON::XS; +use Digest::MD5 qw(md5_hex); + +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 => { + 'client-id:s' => { name => 'client_id' }, + 'client-secret:s' => { name => 'client_secret' }, + 'hostname:s' => { name => 'hostname' }, + 'port:s' => { name => 'port' }, + 'proto:s' => { name => 'proto' }, + 'timeout:s' => { name => 'timeout' }, + 'unknown-http-status:s' => { name => 'unknown_http_status' }, + 'warning-http-status:s' => { name => 'warning_http_status' }, + 'critical-http-status:s' => { name => 'critical_http_status' } + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'REST API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{http} = centreon::plugins::http->new(%options); + + return $self; +} + +sub set_options { + my ($self, %options) = @_; + + $self->{option_results} = $options{option_results}; +} + +sub set_defaults {} + +sub check_options { + my ($self, %options) = @_; + + $self->{hostname} = (defined($self->{option_results}->{hostname})) ? $self->{option_results}->{hostname} : ''; + $self->{port} = (defined($self->{option_results}->{port})) ? $self->{option_results}->{port} : 8443; + $self->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'https'; + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 10; + $self->{client_id} = (defined($self->{option_results}->{client_id})) ? $self->{option_results}->{client_id} : ''; + $self->{client_secret} = (defined($self->{option_results}->{client_secret})) ? $self->{option_results}->{client_secret} : ''; + $self->{unknown_http_status} = (defined($self->{option_results}->{unknown_http_status})) ? $self->{option_results}->{unknown_http_status} : '%{http_code} < 200 or %{http_code} >= 300'; + $self->{warning_http_status} = (defined($self->{option_results}->{warning_http_status})) ? $self->{option_results}->{warning_http_status} : ''; + $self->{critical_http_status} = (defined($self->{option_results}->{critical_http_status})) ? $self->{option_results}->{critical_http_status} : ''; + + if ($self->{hostname} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --hostname option."); + $self->{output}->option_exit(); + } + if ($self->{client_id} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --client-id option."); + $self->{output}->option_exit(); + } + if ($self->{client_secret} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --client-secret option."); + $self->{output}->option_exit(); + } + + return 0; +} + +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}->{timeout} = $self->{timeout}; +} + +sub get_hostname { + my ($self, %options) = @_; + + return $self->{hostname}; +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->add_header(key => 'Content-Type', value => 'application/json'); + $self->{http}->add_header(key => 'Accept', value => 'application/json'); + if (defined($self->{access_token})) { + $self->{http}->add_header(key => 'Authorization', value => 'Bearer ' . $self->{access_token}); + } + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub get_access_token { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read( + statefile => + 'cisco_ssms_api_' . + $self->get_hostname() . '_' . + md5_hex($self->{client_id}) + ); + my $expires_on = $options{statefile}->get(name => 'expires_on'); + my $access_token = $options{statefile}->get(name => 'access_token'); + + if ($has_cache_file == 0 || !defined($access_token) || (($expires_on - time()) < 10)) { + my $json_request = { + grant_type => 'client_credentials', + client_id => $self->{client_id}, + client_secret => $self->{client_secret} + }; + my $encoded; + eval { + $encoded = encode_json($json_request); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot encode json request"); + $self->{output}->option_exit(); + } + + $self->settings(); + + my ($content) = $self->{http}->request( + method => 'POST', + url_path => '/backend/oauth/token', + query_form_post => $encoded, + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + ); + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode response (add --debug option to display returned content)"); + $self->{output}->option_exit(); + } + + $access_token = $decoded->{access_token}; + my $datas = { last_timestamp => time(), access_token => $decoded->{access_token}, expires_on => (time() + $decoded->{expires_in}) }; + $options{statefile}->write(data => $datas); + } + + return $access_token; +} + +sub request_api { + my ($self, %options) = @_; + + if (!defined($self->{access_token})) { + $self->{access_token} = $self->get_access_token(statefile => $self->{cache}); + } + + $self->settings(); + + my ($content) = $self->{http}->request( + method => 'POST', + url_path => $options{endpoint}, + query_form_post => encode_json({ limit => -1 }), + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + ); + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode response (add --debug option to display returned content)"); + $self->{output}->option_exit(); + } + + return $decoded; +} + +sub get_licenses { + my ($self, %options) = @_; + + #my $plop = do { + # local $/ = undef; + # if (!open my $fh, "<", '/tmp/licenses.pretty') { + # $self->{output}->add_option_msg(short_msg => "Could not open file $self->{option_results}->{$_} : $!"); + # $self->{output}->option_exit(); + # } + # <$fh>; + #}; + #$plop = JSON::XS->new->utf8->decode($plop); + #return $plop; + + return $self->request_api(endpoint => '/api/v1/accounts/' . $options{account} . '/licenses'); +} + +1; + +__END__ + +=head1 NAME + +Cisco SSMS Rest API + +=head1 REST API OPTIONS + +Cisco SSMS Rest API + +=over 8 + +=item B<--hostname> + +Set hostname. + +=item B<--port> + +Port used (Default: 8443) + +=item B<--proto> + +Specify https if needed (Default: 'https') + +=item B<--client-id> + +Set client ID. + +=item B<--client-secret> + +Set client secret. + +=item B<--timeout> + +Set timeout in seconds (Default: 10). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/network/cisco/ssms/restapi/mode/licenses.pm b/network/cisco/ssms/restapi/mode/licenses.pm new file mode 100644 index 000000000..1cea04425 --- /dev/null +++ b/network/cisco/ssms/restapi/mode/licenses.pm @@ -0,0 +1,226 @@ +# +# Copyright 2020 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 network::cisco::ssms::restapi::mode::licenses; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold); + +sub custom_status_output { + my ($self, %options) = @_; + + return sprintf( + "status: '%s'", + $self->{result_values}->{status} + ); +} + +sub custom_license_output { + my ($self, %options) = @_; + + return sprintf( + "usage total: %s used: %s (%.2f%%) free: %s (%.2f%%)", + $self->{result_values}->{total}, + $self->{result_values}->{used}, + $self->{result_values}->{prct_used}, + $self->{result_values}->{free}, + $self->{result_values}->{prct_free} + ); +} + +sub account_long_output { + my ($self, %options) = @_; + + return "checking account '" . $options{instance_value}->{display} . "'"; +} + +sub prefix_account_output { + my ($self, %options) = @_; + + return "account '" . $options{instance_value}->{display} . "' "; +} + +sub prefix_license_output { + my ($self, %options) = @_; + + return "license '" . $options{instance_value}->{display} . "' "; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'accounts', type => 3, cb_prefix_output => 'prefix_account_output', cb_long_output => 'account_long_output', indent_long_output => ' ', message_multiple => 'All accounts are ok', + group => [ + { name => 'licenses', display_long => 1, cb_prefix_output => 'prefix_license_output', message_multiple => 'licenses are ok', type => 1, skipped_code => { -10 => 1 } } + ] + } + ]; + + $self->{maps_counters}->{licenses} = [ + { label => 'license-status', threshold => 0, set => { + key_values => [ { 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 + } + }, + { label => 'usage', nlabel => 'licenses.usage.count', set => { + key_values => [ { name => 'used' }, { name => 'free' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], + closure_custom_output => $self->can('custom_license_output'), + perfdatas => [ + { template => '%d', min => 0, max => 'total', label_extra_instance => 1 } + ] + } + }, + { label => 'usage-free', display_ok => 0, nlabel => 'licenses.free.count', set => { + key_values => [ { name => 'free' }, { name => 'used' }, { name => 'prct_used' }, { name => 'prct_free' }, { name => 'total' } ], + closure_custom_output => $self->can('custom_license_output'), + perfdatas => [ + { template => '%d', min => 0, max => 'total', label_extra_instance => 1 } + ] + } + }, + { label => 'usage-prct', display_ok => 0, nlabel => 'licenses.usage.percentage', set => { + key_values => [ { name => 'prct_used' } ], + output_template => 'used: %.2f %%', + perfdatas => [ + { template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1 } + ] + } + } + ]; +} + +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 => { + 'account:s@' => { name => 'account' }, + 'filter-license-name:s' => { name => 'filter_license_name' }, + 'unknown-license-status:s' => { name => 'unknown_license_status', default => '' }, + 'warning-license-status:s' => { name => 'warning_license_status', default => '' }, + 'critical-license-status:s' => { name => 'critical_license_status', default => '%{status} !~ /in compliance/i' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + $self->{account_names} = []; + if (defined($self->{option_results}->{account})) { + foreach my $account (@{$self->{option_results}->{account}}) { + push @{$self->{account_names}}, $account if ($account ne ''); + } + } + if (scalar(@{$self->{account_names}}) <= 0) { + $self->{output}->add_option_msg(short_msg => 'need to specify --account option.'); + $self->{output}->option_exit(); + } + + $self->change_macros(macros => ['warning_license_status', 'critical_license_status', 'unknown_license_status']); +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{accounts} = {}; + foreach my $account (@{$self->{account_names}}) { + my $results = $options{custom}->get_licenses( + account => $account + ); + + next if (!defined($results->{licenses})); + + $self->{accounts}->{$account} = { + display => $account, + licenses => {} + }; + foreach (@{$results->{licenses}}) { + next if (defined($self->{option_results}->{filter_license_name}) && $self->{option_results}->{filter_license_name} ne '' && + $_->{license} !~ /$self->{option_results}->{filter_license_name}/); + + $self->{accounts}->{$account}->{licenses}->{ $_->{license} } = { + display => $_->{license}, + status => $_->{status}, + used => $_->{inUse}, + free => $_->{available}, + total => $_->{quantity}, + prct_used => $_->{inUse} * 100 / $_->{quantity}, + prct_free => $_->{available} * 100 / $_->{quantity} + }; + } + } +} + +1; + +__END__ + +=head1 MODE + +Check licenses. + +=over 8 + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='status' + +=item B<--account> + +Check account name (Required. Multiple option). + +=item B<--filter-license-name> + +Filter license name (can be a regexp). + +=item B<--unknown-license-status> + +Set unknown threshold for status. +Can used special variables like: %{status}, %{display} + +=item B<--warning-license-status> + +Set warning threshold for status. +Can used special variables like: %{status}, %{display} + +=item B<--critical-license-status> + +Set critical threshold for status (Default: '%{status} !~ /in compliance/i'). +Can used special variables like: %{status}, %{display} + +=item B<--warning-*> B<--critical-*> + +Thresholds. +Can be: 'usage' (B), 'usage-free' (B), 'usage-prct' (%). + +=back + +=cut diff --git a/network/cisco/ssms/restapi/plugin.pm b/network/cisco/ssms/restapi/plugin.pm new file mode 100644 index 000000000..373484aaf --- /dev/null +++ b/network/cisco/ssms/restapi/plugin.pm @@ -0,0 +1,49 @@ +# +# Copyright 2020 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 network::cisco::ssms::restapi::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} = '0.1'; + $self->{modes} = { + 'licenses' => 'network::cisco::ssms::restapi::mode::licenses' + }; + + $self->{custom_modes}->{api} = 'network::cisco::ssms::restapi::custom::api'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Cisco Smart Software Manager Satellite in Rest API. + +=cut