diff --git a/packaging/centreon-plugin-Applications-Exense-Step-Restapi/deb.json b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/deb.json new file mode 100644 index 0000000000..9757fe1126 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/deb.json @@ -0,0 +1,4 @@ +{ + "dependencies": [ + ] +} diff --git a/packaging/centreon-plugin-Applications-Exense-Step-Restapi/pkg.json b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/pkg.json new file mode 100644 index 0000000000..efc2f2ae78 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/pkg.json @@ -0,0 +1,9 @@ +{ + "pkg_name": "centreon-plugin-Applications-Exense-Step-Restapi", + "pkg_summary": "Centreon Plugin", + "plugin_name": "centreon_exense_step_restapi.pl", + "files": [ + "centreon/plugins/script_custom.pm", + "apps/exense/step/restapi/" + ] +} diff --git a/packaging/centreon-plugin-Applications-Exense-Step-Restapi/rpm.json b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/rpm.json new file mode 100644 index 0000000000..9757fe1126 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Exense-Step-Restapi/rpm.json @@ -0,0 +1,4 @@ +{ + "dependencies": [ + ] +} diff --git a/src/apps/exense/step/restapi/custom/api.pm b/src/apps/exense/step/restapi/custom/api.pm new file mode 100644 index 0000000000..bd792f9be3 --- /dev/null +++ b/src/apps/exense/step/restapi/custom/api.pm @@ -0,0 +1,336 @@ +# +# Copyright 2024 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::exense::step::restapi::custom::api; + +use strict; +use warnings; +use centreon::plugins::http; +use centreon::plugins::statefile; +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 => { + 'hostname:s' => { name => 'hostname' }, + 'port:s' => { name => 'port' }, + 'proto:s' => { name => 'proto' }, + 'api-username:s' => { name => 'api_username' }, + 'api-password:s' => { name => 'api_password' }, + 'token:s' => { name => 'token' }, + 'timeout:s' => { name => 'timeout' } + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'REST API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{http} = centreon::plugins::http->new(%options, default_backend => 'curl'); + $self->{cache_connect} = centreon::plugins::statefile->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->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'https'; + $self->{port} = (defined($self->{option_results}->{port})) ? $self->{option_results}->{port} : 443; + $self->{api_username} = (defined($self->{option_results}->{api_username})) ? $self->{option_results}->{api_username} : ''; + $self->{api_password} = (defined($self->{option_results}->{api_password})) ? $self->{option_results}->{api_password} : ''; + $self->{token} = (defined($self->{option_results}->{token})) ? $self->{option_results}->{token} : ''; + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 30; + $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->{token} ne '') { + return 0; + } + + if ($self->{api_username} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --api-username or --token option."); + $self->{output}->option_exit(); + } + if ($self->{api_password} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --api-password option."); + $self->{output}->option_exit(); + } + + $self->{cache_connect}->check_options(option_results => $self->{option_results}); + return 0; +} + +sub get_connection_infos { + my ($self, %options) = @_; + + return $self->{hostname} . '_' . $self->{http}->get_port(); +} + +sub get_hostname { + my ($self, %options) = @_; + + return $self->{hostname}; +} + +sub get_port { + my ($self, %options) = @_; + + return $self->{port}; +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->decode($self->{output}->decode($options{content})); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); + $self->{output}->option_exit(); + } + + return $decoded; +} + +sub build_options_for_httplib { + my ($self, %options) = @_; + + $self->{option_results}->{hostname} = $self->{hostname}; + $self->{option_results}->{port} = $self->{port}; + $self->{option_results}->{proto} = $self->{proto}; +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->add_header(key => 'Accept', value => 'application/json'); + $self->{http}->add_header(key => 'Content-Type', value => 'application/json'); + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub clean_session_id { + my ($self, %options) = @_; + + my $datas = { updated => time() }; + $self->{cache_connect}->write(data => $datas); +} + +sub get_session_id { + my ($self, %options) = @_; + + my $has_cache_file = $self->{cache_connect}->read(statefile => 'exense_step_' . md5_hex($self->{option_results}->{hostname}) . '_' . md5_hex($self->{option_results}->{api_username})); + my $session_id = $self->{cache_connect}->get(name => 'session_id'); + my $md5_secret_cache = $self->{cache_connect}->get(name => 'md5_secret'); + my $md5_secret = md5_hex($self->{api_username} . $self->{api_password}); + + if ($has_cache_file == 0 || + !defined($session_id) || + (defined($md5_secret_cache) && $md5_secret_cache ne $md5_secret) + ) { + my $json_request = { username => $self->{api_username}, password => $self->{api_password} }; + my $encoded; + eval { + $encoded = encode_json($json_request); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => 'cannot encode json request'); + $self->{output}->option_exit(); + } + + my ($content) = $self->{http}->request( + method => 'POST', + url_path => '/rest/access/login', + query_form_post => $encoded, + warning_status => '', unknown_status => '', critical_status => '' + ); + + if ($self->{http}->get_code() != 200) { + $self->{output}->add_option_msg(short_msg => "Authentication error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + $self->{output}->option_exit(); + } + + my (@cookies) = $self->{http}->get_first_header(name => 'Set-Cookie'); + foreach my $cookie (@cookies) { + $session_id = $1 if ($cookie =~ /sessionid=(.+?);/); + } + + if (!defined($session_id)) { + $self->{output}->add_option_msg(short_msg => "Cannot get cookie"); + $self->{output}->option_exit(); + } + + my $datas = { + updated => time(), + session_id => $session_id, + md5_secret => $md5_secret + }; + $self->{cache_connect}->write(data => $datas); + } + + return $session_id; +} + +sub credentials { + my ($self, %options) = @_; + + my $creds = {}; + if ($self->{token} ne '') { + $creds = { + header => ['Authorization: Bearer ' . $self->{token}], + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + }; + } else { + my $session_id = $self->get_session_id(); + $creds = { + header => ['Cookie: sessionid=' . $session_id], + warning_status => '', + unknown_status => '', + critical_status => '' + }; + } + + return $creds; +} + +sub request { + my ($self, %options) = @_; + + my $endpoint = $options{endpoint}; + + $self->settings(); + my $creds = $self->credentials(); + + my $content = $self->{http}->request( + method => $options{method}, + url_path => $endpoint, + get_param => $options{get_param}, + query_form_post => $options{query_form_post}, + %$creds + ); + + # Maybe there is an issue with the token. So we retry. + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->clean_session_id(); + $creds = $self->credentials(); + $creds->{unknown_status} = $self->{unknown_http_status}; + $creds->{warning_status} = $self->{warning_status}; + $creds->{critical_http_status} = $self->{critical_http_status}; + $content = $self->{http}->request( + method => $options{method}, + url_path => $endpoint, + get_param => $options{get_param}, + query_form_post => $options{query_form_post}, + %$creds + ); + } + + return if (defined($options{skip_decode})); + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded)) { + $self->{output}->add_option_msg(short_msg => 'Error while retrieving data (add --debug option for detailed message)'); + $self->{output}->option_exit(); + } + + return $decoded; +} + +1; + +__END__ + +=head1 NAME + +Exense Step API + +=head1 SYNOPSIS + +Exense Step api + +=head1 REST API OPTIONS + +=over 8 + +=item B<--hostname> + +API hostname. + +=item B<--port> + +API port (default: 443) + +=item B<--proto> + +Specify https if needed (default: 'https') + +=item B<--token> + +Use token authentication. + +=item B<--api-username> + +Set API username + +=item B<--api-password> + +Set API password + +=item B<--timeout> + +Set HTTP timeout + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/src/apps/exense/step/restapi/mode/listplans.pm b/src/apps/exense/step/restapi/mode/listplans.pm new file mode 100644 index 0000000000..3bb7a6e919 --- /dev/null +++ b/src/apps/exense/step/restapi/mode/listplans.pm @@ -0,0 +1,149 @@ +# +# Copyright 2024 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::exense::step::restapi::mode::listplans; + +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 => { + 'tenant-name:s' => { name => 'tenant_name' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); + + if (!defined($self->{option_results}->{tenant_name}) || $self->{option_results}->{tenant_name} eq '') { + $self->{option_results}->{tenant_name} = '[All]'; + } +} + +sub manage_selection { + my ($self, %options) = @_; + + my $payload = $self->{option_results}->{tenant_name}; + $options{custom}->request(method => 'POST', endpoint => '/rest/tenants/current', query_form_post => $payload, skip_decode => 1); + + $payload = { + skip => 0, + limit => 4000000, + filters => [ + { + collectionFilter => { type => 'True', field => 'visible' } + } + ], + 'sort' => { + 'field' => 'attributes.name', + 'direction' => 'ASCENDING' + } + }; + eval { + $payload = encode_json($payload); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => 'cannot encode json request'); + $self->{output}->option_exit(); + } + + my $plans = $options{custom}->request(method => 'POST', endpoint => '/rest/table/plans', query_form_post => $payload); + + my $results = []; + foreach my $plan (@{$plans->{data}}) { + # skip plans created by keyword single execution + next if ($plan->{visible} =~ /false|0/); + + push @$results, { + id => $plan->{id}, + name => $plan->{attributes}->{name} + }; + } + + return $results; +} + +sub run { + my ($self, %options) = @_; + + my $results = $self->manage_selection(%options); + foreach (@$results) { + $self->{output}->output_add( + long_msg => sprintf( + '[id: %s][name: %s]', + $_->{id}, + $_->{name} + ) + ); + } + $self->{output}->output_add( + severity => 'OK', + short_msg => 'List plans:' + ); + + $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 => ['id', 'name']); +} + +sub disco_show { + my ($self, %options) = @_; + + my $results = $self->manage_selection(%options); + foreach (@$results) { + $self->{output}->add_disco_entry( + id => $_->{id}, + name => $_->{name} + ); + } +} + +1; + +__END__ + +=head1 MODE + +List plans. + +=over 8 + +=item B<--tenant-name> + +Check plan of a tenant (default: '[All]'). + +=back + +=cut diff --git a/src/apps/exense/step/restapi/mode/listtenants.pm b/src/apps/exense/step/restapi/mode/listtenants.pm new file mode 100644 index 0000000000..6c88b13419 --- /dev/null +++ b/src/apps/exense/step/restapi/mode/listtenants.pm @@ -0,0 +1,111 @@ +# +# Copyright 2024 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::exense::step::restapi::mode::listtenants; + +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 => {}); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub manage_selection { + my ($self, %options) = @_; + + my $tenants = $options{custom}->request(method => 'GET', endpoint => '/rest/tenants'); + + my $results = []; + foreach my $tenant (@$tenants) { + push @$results, { + name => $tenant->{name}, + projectId => defined($tenant->{projectId}) ? $tenant->{projectId} : '', + global => $tenant->{global} =~ /true|1/i ? 1 : 0 + }; + } + + return $results; +} + +sub run { + my ($self, %options) = @_; + + my $results = $self->manage_selection(%options); + foreach (@$results) { + $self->{output}->output_add( + long_msg => sprintf( + '[name: %s][projectId: %s][global: %s]', + $_->{name}, + $_->{projectId}, + $_->{global} + ) + ); + } + $self->{output}->output_add( + severity => 'OK', + short_msg => 'List tenants:' + ); + + $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 => ['name', 'projectId', 'global']); +} + +sub disco_show { + my ($self, %options) = @_; + + my $results = $self->manage_selection(%options); + foreach (@$results) { + $self->{output}->add_disco_entry(%$_); + } +} + +1; + +__END__ + +=head1 MODE + +List tenants. + +=over 8 + +=back + +=cut diff --git a/src/apps/exense/step/restapi/mode/plans.pm b/src/apps/exense/step/restapi/mode/plans.pm new file mode 100644 index 0000000000..a70eaac146 --- /dev/null +++ b/src/apps/exense/step/restapi/mode/plans.pm @@ -0,0 +1,472 @@ +# +# Copyright 2024 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::exense::step::restapi::mode::plans; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use DateTime; +use POSIX; +use JSON::XS; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); +use centreon::plugins::misc; + +my $unitdiv = { s => 1, w => 604800, d => 86400, h => 3600, m => 60 }; +my $unitdiv_long = { s => 'seconds', w => 'weeks', d => 'days', h => 'hours', m => 'minutes' }; + +sub custom_last_exec_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add( + nlabel => $self->{nlabel} . '.' . $unitdiv_long->{ $self->{instance_mode}->{option_results}->{unit} }, + instances => $self->{result_values}->{name}, + unit => $self->{instance_mode}->{option_results}->{unit}, + value => $self->{result_values}->{lastExecSeconds} >= 0 ? floor($self->{result_values}->{lastExecSeconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }) : $self->{result_values}->{lastExecSeconds}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), + min => 0 + ); +} + +sub custom_last_exec_threshold { + my ($self, %options) = @_; + + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{lastExecSeconds} >= 0 ? floor($self->{result_values}->{lastExecSeconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }) : $self->{result_values}->{lastExecSeconds}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' }, + { label => 'unknown-'. $self->{thlabel}, exit_litteral => 'unknown' } + ] + ); +} + +sub custom_duration_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add( + nlabel => $self->{nlabel} . '.' . $unitdiv_long->{ $self->{instance_mode}->{option_results}->{unit} }, + instances => $self->{result_values}->{name}, + unit => $self->{instance_mode}->{option_results}->{unit}, + value => floor($self->{result_values}->{durationSeconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), + min => 0 + ); +} + +sub custom_duration_threshold { + my ($self, %options) = @_; + + return $self->{perfdata}->threshold_check( + value => floor($self->{result_values}->{durationSeconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }), + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' }, + { label => 'unknown-'. $self->{thlabel}, exit_litteral => 'unknown' } + ] + ); +} + +sub custom_execution_status_output { + my ($self, %options) = @_; + + return sprintf( + "result: %s, status: %s", + $self->{result_values}->{result}, + $self->{result_values}->{status} + ); +} + +sub plan_long_output { + my ($self, %options) = @_; + + return sprintf( + "checking plan '%s'", + $options{instance_value}->{name} + ); +} + +sub prefix_plan_output { + my ($self, %options) = @_; + + return sprintf( + "plan '%s' ", + $options{instance_value}->{name} + ); +} + +sub prefix_global_output { + my ($self, %options) = @_; + + return 'Number of plans '; +} + +sub prefix_execution_output { + my ($self, %options) = @_; + + return sprintf( + "execution '%s' [env: %s] [started: %s] ", + $options{instance_value}->{executionId}, + $options{instance_value}->{environment}, + $options{instance_value}->{started} + ); +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, cb_prefix_output => 'prefix_global_output' }, + { name => 'plans', type => 3, cb_prefix_output => 'prefix_plan_output', cb_long_output => 'prefix_plan_output', indent_long_output => ' ', message_multiple => 'All plans are ok', + group => [ + { name => 'exec_detect', type => 0 }, + { name => 'failed', type => 0 }, + { name => 'timers', type => 0, skipped_code => { -10 => 1 } }, + { name => 'executions', type => 1, cb_prefix_output => 'prefix_execution_output', message_multiple => 'executions are ok', display_long => 1, sort_method => 'num', skipped_code => { -10 => 1 } } + ] + } + ]; + + $self->{maps_counters}->{global} = [ + { label => 'plans-detected', display_ok => 0, nlabel => 'plans.detected.count', set => { + key_values => [ { name => 'detected' } ], + output_template => 'detected: %s', + perfdatas => [ + { template => '%s', min => 0 } + ] + } + } + ]; + + $self->{maps_counters}->{exec_detect} = [ + { label => 'plan-executions-detected', nlabel => 'plan.executions.detected.count', set => { + key_values => [ { name => 'detected' }, { name => 'name' } ], + output_template => 'number of plan executions detected: %s', + perfdatas => [ + { template => '%s', min => 0, label_extra_instance => 1, instance_use => 'name' } + ] + } + } + ]; + + $self->{maps_counters}->{failed} = [ + { label => 'plan-executions-failed-prct', nlabel => 'plan.executions.failed.percentage', set => { + key_values => [ { name => 'failedPrct' }, { name => 'name' } ], + output_template => 'number of failed executions: %.2f %%', + perfdatas => [ + { template => '%.2f', unit => '%', min => 0, max => 100, label_extra_instance => 1, instance_use => 'name' } + ] + } + } + ]; + + $self->{maps_counters}->{timers} = [ + { label => 'plan-execution-last', nlabel => 'plan.execution.last', set => { + key_values => [ { name => 'lastExecSeconds' }, { name => 'lastExecHuman' }, { name => 'name' } ], + output_template => 'last execution %s', + output_use => 'lastExecHuman', + closure_custom_perfdata => $self->can('custom_last_exec_perfdata'), + closure_custom_threshold_check => $self->can('custom_last_exec_threshold') + } + }, + { label => 'plan-running-duration', nlabel => 'plan.running.duration', set => { + key_values => [ { name => 'durationSeconds' }, { name => 'durationHuman' }, { name => 'name' } ], + output_template => 'running duration %s', + output_use => 'durationHuman', + closure_custom_perfdata => $self->can('custom_duration_perfdata'), + closure_custom_threshold_check => $self->can('custom_duration_threshold') + } + } + ]; + + $self->{maps_counters}->{executions} = [ + { + label => 'plan-execution-status', + type => 2, + set => { + key_values => [ + { name => 'status' }, {name => 'result' }, { name => 'planName' } + ], + closure_custom_output => $self->can('custom_execution_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + } + ]; +} + +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 => { + 'filter-plan-id:s' => { name => 'filter_plan_id' }, + 'filter-plan-name:s' => { name => 'filter_plan_name' }, + 'filter-environment:s' => { name => 'filter_environment' }, + 'since-timeperiod:s' => { name => 'since_timeperiod' }, + 'status-failed:s' => { name => 'status_failed' }, + 'only-last-execution' => { name => 'only_last_execution' }, + 'tenant-name:s' => { name => 'tenant_name' }, + 'timezone:s' => { name => 'timezone' }, + 'unit:s' => { name => 'unit', default => 's' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if ($self->{option_results}->{unit} eq '' || !defined($unitdiv->{$self->{option_results}->{unit}})) { + $self->{option_results}->{unit} = 's'; + } + + if (!defined($self->{option_results}->{since_timeperiod}) || $self->{option_results}->{since_timeperiod} eq '') { + $self->{option_results}->{since_timeperiod} = 86400; + } + + if (!defined($self->{option_results}->{tenant_name}) || $self->{option_results}->{tenant_name} eq '') { + $self->{option_results}->{tenant_name} = '[All]'; + } + + $self->{tz} = {}; + if (defined($self->{option_results}->{timezone}) && $self->{option_results}->{timezone} ne '') { + $self->{tz} = centreon::plugins::misc::set_timezone(name => $self->{option_results}->{timezone}); + } + + $self->{option_results}->{timezone} = 'UTC' if (!defined($self->{option_results}->{timezone}) || $self->{option_results}->{timezone} +eq ''); + + if (!defined($self->{option_results}->{status_failed}) || $self->{option_results}->{status_failed} eq '') { + $self->{option_results}->{status_failed} = '%{result} =~ /technical_error|failed|interrupted/i'; + } + + $self->{option_results}->{status_failed} =~ s/%\{(.*?)\}/\$values->{$1}/g; + $self->{option_results}->{status_failed} =~ s/%\((.*?)\)/\$values->{$1}/g; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $status_filter = {}; + if (defined($self->{option_results}->{filter_status}) && $self->{option_results}->{filter_status}[0] ne '') { + $status_filter->{statusFilter} = $self->{option_results}->{filter_status}; + } + + my $payload = $self->{option_results}->{tenant_name}; + $options{custom}->request(method => 'POST', endpoint => '/rest/tenants/current', query_form_post => $payload, skip_decode => 1); + + $payload = { + skip => 0, + limit => 4000000, + filters => [ + { + collectionFilter => { type => 'True', field => 'visible' } + } + ], + 'sort' => { + 'field' => 'attributes.name', + 'direction' => 'ASCENDING' + } + }; + eval { + $payload = encode_json($payload); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => 'cannot encode json request'); + $self->{output}->option_exit(); + } + + my $plans = $options{custom}->request(method => 'POST', endpoint => '/rest/table/plans', query_form_post => $payload); + + my $ctime = time(); + my $filterTime = ($ctime - $self->{option_results}->{since_timeperiod}) * 1000; + + $payload = { + skip => 0, + limit => 4000000, + filters => [ + { + collectionFilter => { type => 'Gte', field => 'startTime', value => $filterTime } + } + ], + 'sort' => { + 'field' => 'startTime', + 'direction' => 'DESCENDING' + } + }; + eval { + $payload = encode_json($payload); + }; + if ($@) { + $self->{output}->add_option_msg(short_msg => 'cannot encode json request'); + $self->{output}->option_exit(); + } + + my $executions = $options{custom}->request(method => 'POST', endpoint => '/rest/table/executions', query_form_post => $payload); + + $self->{global} = { detected => 0 }; + $self->{plans} = {}; + foreach my $plan (@{$plans->{data}}) { + # skip plans created by keyword single execution + next if ($plan->{visible} =~ /false|0/); + next if (defined($self->{option_results}->{filter_plan_id}) && $self->{option_results}->{filter_plan_id} ne '' && + $plan->{id} !~ /$self->{option_results}->{filter_plan_id}/); + next if (defined($self->{option_results}->{filter_plan_name}) && $self->{option_results}->{filter_plan_name} ne '' && + $plan->{attributes}->{name} !~ /$self->{option_results}->{filter_plan_name}/); + + $self->{global}->{detected}++; + + $self->{plans}->{ $plan->{id} } = { + name => $plan->{attributes}->{name}, + exec_detect => { detected => 0, name => $plan->{attributes}->{name} }, + failed => { failedPrct => 0, name => $plan->{attributes}->{name} }, + timers => {}, + executions => {} + }; + + my ($last_exec, $older_running_exec); + my ($failed, $total) = (0, 0); + my $i = 0; + foreach my $plan_exec (@{$executions->{data}}) { + next if (!defined($plan_exec->{planId})); + next if ($plan_exec->{planId} ne $plan->{id}); + $plan_exec->{startTimeSec} = $plan_exec->{startTime} / 1000; + next if ($plan_exec->{startTimeSec} < ($ctime - $self->{option_results}->{since_timeperiod})); + next if (defined($self->{option_results}->{filter_environment}) && $self->{option_results}->{filter_environment} ne '' && + $plan_exec->{executionParameters}->{customParameters}->{env} !~ /$self->{option_results}->{filter_environment}/); + + if (!defined($plan_exec->{endTime}) || $plan_exec->{endTime} eq '') { + $older_running_exec = $plan_exec; + } + if (!defined($last_exec)) { + $last_exec = $plan_exec; + } + + $self->{plans}->{ $plan->{id} }->{exec_detect}->{detected}++; + $failed++ if ($self->{output}->test_eval(test => $self->{option_results}->{status_failed}, values => { result => lc($plan_exec->{result}), status => lc($plan_exec->{status}) })); + $total++; + + my $dt = DateTime->from_epoch(epoch => $plan_exec->{startTimeSec}, %{$self->{tz}}); + my $timeraised = sprintf( + '%02d-%02d-%02dT%02d:%02d:%02d (%s)', $dt->year, $dt->month, $dt->day, $dt->hour, $dt->minute, $dt->second, $self->{option_results}->{timezone} + ); + $self->{plans}->{ $plan->{id} }->{executions}->{$i} = { + executionId => $plan_exec->{id}, + planName => $plan->{attributes}->{name}, + environment => $plan_exec->{executionParameters}->{customParameters}->{env}, + started => $timeraised, + status => lc($plan_exec->{status}), + result => lc($plan_exec->{result}) + }; + $i++; + + last if (defined($self->{option_results}->{only_last_execution})); + } + + $self->{plans}->{ $plan->{id} }->{failed}->{failedPrct} = $total > 0 ? $failed * 100 / $total : 0; + + if (defined($last_exec)) { + $self->{plans}->{ $plan->{id} }->{timers} = { + name => $plan->{attributes}->{name}, + lastExecSeconds => defined($last_exec->{startTime}) ? $ctime - $last_exec->{startTimeSec} : -1, + lastExecHuman => defined($last_exec->{startTime}) ? centreon::plugins::misc::change_seconds(value => $ctime - $last_exec->{startTimeSec}) : 'never' + }; + } + + if (defined($older_running_exec)) { + my $duration = $ctime - $older_running_exec->{startTime}; + $self->{plans}->{ $plan->{name} }->{timers}->{durationSeconds} = $duration; + $self->{plans}->{ $plan->{name} }->{timers}->{durationHuman} = centreon::plugins::misc::change_seconds(value => $duration); + } + } +} + +1; + +__END__ + +=head1 MODE + +Check plans. + +=over 8 + +=item B<--tenant-name> + +Check plan of a tenant (default: '[All]'). + +=item B<--filter-plan-id> + +Filter plans by plan ID. + +=item B<--filter-plan-name> + +Filter plans by plan name. + +=item B<--filter-environment> + +Filter plan executions by environment name. + +=item B<--since-timeperiod> + +Time period to get plans execution informations (in seconds. default: 86400). + +=item B<--only-last-execution> + +Check only last plan execution. + +=item B<--timezone> + +Define timezone for start/end plan execution time (default is 'UTC'). + +=item B<--status-failed> + +Expression to define status failed (default: '%{result} =~ /technical_error|failed|interrupted/i'). + +=item B<--unknown-plan-execution-status> + +Set unknown threshold for last plan execution status. +You can use the following variables: %{status}, %{planName} + +=item B<--warning-plan-execution-status> + +Set warning threshold for last plan execution status. +You can use the following variables: %{status}, %{planName} + +=item B<--critical-plan-execution-status> + +Set critical threshold for last plan execution status. +You can use the following variables: %{status}, %{planName} + +=item B<--warning-*> B<--critical-*> + +Thresholds. +Can be: 'plans-detected', 'plans-executions-detected', 'plan-executions-failed-prct', +'plan-execution-last', 'plan-running-duration'. + +=back + +=cut diff --git a/src/apps/exense/step/restapi/plugin.pm b/src/apps/exense/step/restapi/plugin.pm new file mode 100644 index 0000000000..72ff0af8ed --- /dev/null +++ b/src/apps/exense/step/restapi/plugin.pm @@ -0,0 +1,51 @@ +# +# Copyright 2024 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::exense::step::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->{modes} = { + 'list-plans' => 'apps::exense::step::restapi::mode::listplans', + 'list-tenants' => 'apps::exense::step::restapi::mode::listtenants', + 'plans' => 'apps::exense::step::restapi::mode::plans' + }; + + $self->{custom_modes}->{api} = 'apps::exense::step::restapi::custom::api'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Exense Step using Rest API. + +=cut diff --git a/src/hold for last plan execution status. b/src/hold for last plan execution status. new file mode 100644 index 0000000000..333a0b576c --- /dev/null +++ b/src/hold for last plan execution status. @@ -0,0 +1,258 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^W WRAP search if no match found. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-M_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --incsearch + Search file as each pattern character is typed in. + --line-num-width=N + Set the width of the -N line number field to N characters. + --mouse + Enable mouse input. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --rscroll=C + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --status-col-width=N + Set the width of the -J status column to N characters. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=N + Each click of the mouse wheel moves N lines. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all.