543 lines
19 KiB
Perl
543 lines
19 KiB
Perl
#
|
|
# Copyright 2021 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::sahipro::restapi::mode::scenario;
|
|
|
|
use base qw(centreon::plugins::templates::counter);
|
|
|
|
use strict;
|
|
use warnings;
|
|
use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng);
|
|
use centreon::plugins::http;
|
|
use Time::HiRes;
|
|
use POSIX qw(strftime);
|
|
use XML::Simple;
|
|
use DateTime;
|
|
|
|
my %handlers = (ALRM => {});
|
|
|
|
sub custom_status_output {
|
|
my ($self, %options) = @_;
|
|
|
|
return sprintf('status is %s', $self->{result_values}->{status});
|
|
}
|
|
|
|
sub custom_status_calc {
|
|
my ($self, %options) = @_;
|
|
|
|
foreach (keys %{$options{new_datas}}) {
|
|
if (/$self->{instance}_(step\d+_time)/) {
|
|
$self->{result_values}->{$1} = $options{new_datas}->{$_};
|
|
}
|
|
}
|
|
|
|
$self->{result_values}->{status} = $options{new_datas}->{$self->{instance} . '_status'};
|
|
return 0;
|
|
}
|
|
|
|
sub prefix_global_output {
|
|
my ($self, %options) = @_;
|
|
|
|
return 'Scenario ';
|
|
}
|
|
|
|
sub prefix_step_output {
|
|
my ($self, %options) = @_;
|
|
|
|
return "Step '" . $options{instance_value}->{step} . "' [" . $options{instance_value}->{display} . "] ";
|
|
}
|
|
|
|
sub set_counters {
|
|
my ($self, %options) = @_;
|
|
|
|
$self->{maps_counters_type} = [
|
|
{ name => 'global', type => 0, cb_prefix_output => 'prefix_global_output', skipped_code => { -10 => 1 } },
|
|
{ name => 'steps', type => 1, cb_prefix_output => 'prefix_step_output', message_multiple => 'All steps are ok', sort_method => 'num' }
|
|
];
|
|
|
|
$self->{maps_counters}->{global} = [
|
|
{
|
|
label => 'status',
|
|
type => 2,
|
|
critical_default => '%{status} ne "SUCCESS"',
|
|
set => {
|
|
key_values => [],
|
|
manual_keys => 1,
|
|
closure_custom_calc => $self->can('custom_status_calc'),
|
|
closure_custom_output => $self->can('custom_status_output'),
|
|
closure_custom_perfdata => sub { return 0; },
|
|
closure_custom_threshold_check => \&catalog_status_threshold_ng
|
|
}
|
|
},
|
|
{ label => 'total-time', nlabel => 'scenario.execution.time.seconds', set => {
|
|
key_values => [ { name => 'time_taken' } ],
|
|
output_template => 'execution time: %s ms',
|
|
perfdatas => [
|
|
{ label => 'total_time', template => '%s', min => 0, unit => 'ms' }
|
|
]
|
|
}
|
|
},
|
|
{ label => 'total-steps', nlabel => 'scenario.steps.count', set => {
|
|
key_values => [ { name => 'total_steps' } ],
|
|
output_template => 'total steps: %s',
|
|
perfdatas => [
|
|
{ label => 'total_steps', template => '%s', min => 0 }
|
|
]
|
|
}
|
|
},
|
|
{ label => 'failures', nlabel => 'scenario.failures.count', set => {
|
|
key_values => [ { name => 'failures' } ],
|
|
output_template => 'failures: %s',
|
|
perfdatas => [
|
|
{ label => 'failures', template => '%s', min => 0 }
|
|
]
|
|
}
|
|
},
|
|
{ label => 'errors', nlabel => 'scenario.errors.count', set => {
|
|
key_values => [ { name => 'errors' } ],
|
|
output_template => 'errors: %s',
|
|
perfdatas => [
|
|
{ label => 'errors', template => '%s', min => 0 }
|
|
]
|
|
}
|
|
},
|
|
];
|
|
|
|
$self->{maps_counters}->{steps} = [
|
|
{ label => 'step-time', nlabel => 'step.execution.time.second', set => {
|
|
key_values => [ { name => 'time_taken' }, { name => 'step' } ],
|
|
output_template => 'execution time: %s ms',
|
|
perfdatas => [
|
|
{ label => 'step_time', template => '%s',
|
|
min => 0, unit => 'ms', label_extra_instance => 1, instance_use => 'step' }
|
|
]
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
sub new {
|
|
my ($class, %options) = @_;
|
|
my $self = $class->SUPER::new(package => __PACKAGE__, %options);
|
|
bless $self, $class;
|
|
|
|
$options{options}->add_options(arguments => {
|
|
'sahi-hostname:s' => { name => 'sahi_hostname' },
|
|
'sahi-port:s' => { name => 'sahi_port', default => 9999 },
|
|
'sahi-proto:s' => { name => 'sahi_proto', default => 'http' },
|
|
'sahi-http-timeout:s' => { name => 'sahi_http_timeout', default => 5 },
|
|
'sahi-endpoint:s' => { name => 'sahi_endpoint', default => '/_s_/dyn/' },
|
|
'sahi-suite:s' => { name => 'sahi_suite' },
|
|
'sahi-threads:s' => { name => 'sahi_threads', default => 1 },
|
|
'sahi-startwith:s' => { name => 'sahi_startwith', default => 'BROWSER' },
|
|
'sahi-browsertype:s' => { name => 'sahi_browsertype', default => 'chrome' },
|
|
'sahi-baseurl:s' => { name => 'sahi_baseurl' },
|
|
'timeout:s' => { name => 'timeout' },
|
|
'retries-scenario-status:s' => { name => 'retries_scenario_status' },
|
|
'interval-scenario-status:s' => { name => 'interval_scenario_status', default => 10 },
|
|
'unknown-run-status:s' => { name => 'unknown_run_status', default => '%{http_code} < 200 or %{http_code} >= 300' },
|
|
'warning-run-status:s' => { name => 'warning_run_status' },
|
|
'critical-run-status:s' => { name => 'critical_run_status', default => '' }
|
|
});
|
|
|
|
$self->{http} = centreon::plugins::http->new(%options);
|
|
$self->set_signal_handlers();
|
|
return $self;
|
|
}
|
|
|
|
sub set_signal_handlers {
|
|
my $self = shift;
|
|
|
|
$SIG{ALRM} = \&class_handle_ALRM;
|
|
$handlers{ALRM}->{$self} = sub { $self->handle_ALRM() };
|
|
}
|
|
|
|
sub class_handle_ALRM {
|
|
foreach (keys %{$handlers{ALRM}}) {
|
|
&{$handlers{ALRM}->{$_}}();
|
|
}
|
|
}
|
|
|
|
sub handle_ALRM {
|
|
my $self = shift;
|
|
|
|
$self->killed_scenario();
|
|
$self->{output}->add_option_msg(short_msg => 'Cannot finished scenario execution (timeout received)');
|
|
$self->{output}->option_exit();
|
|
}
|
|
|
|
sub check_options {
|
|
my ($self, %options) = @_;
|
|
$self->SUPER::check_options(%options);
|
|
|
|
foreach my $option (('sahi_hostname', 'sahi_suite', 'sahi_startwith', 'sahi_browsertype')) {
|
|
(my $label = $option) =~ s/_/-/g;
|
|
if (!defined($self->{option_results}->{$option}) || $self->{option_results}->{$option} eq '') {
|
|
$self->{output}->add_option_msg(short_msg => 'Please set ' . $label . ' option');
|
|
$self->{output}->option_exit();
|
|
}
|
|
}
|
|
|
|
if (!defined($self->{option_results}->{interval_scenario_status}) || $self->{option_results}->{interval_scenario_status} !~ /^\d+$/ ||
|
|
$self->{option_results}->{interval_scenario_status} < 0) {
|
|
$self->{option_results}->{interval_scenario_status} = 10;
|
|
}
|
|
if (!defined($self->{option_results}->{retries_scenario_status}) || $self->{option_results}->{retries_scenario_status} !~ /^\d+$/ ||
|
|
$self->{option_results}->{retries_scenario_status} < 0) {
|
|
$self->{option_results}->{retries_scenario_status} = 0;
|
|
}
|
|
|
|
if (defined($self->{option_results}->{timeout}) && $self->{option_results}->{timeout} =~ /^\d+$/ &&
|
|
$self->{option_results}->{timeout} > 0) {
|
|
alarm($self->{option_results}->{timeout});
|
|
}
|
|
|
|
$self->{http}->set_options(port => $self->{option_results}->{sahi_port}, proto => $self->{option_results}->{sahi_proto});
|
|
}
|
|
|
|
sub decode_xml_response {
|
|
my ($self, %options) = @_;
|
|
|
|
my $content;
|
|
eval {
|
|
$content = XMLin($options{response}, ForceArray => $options{ForceArray}, KeyAttr => []);
|
|
};
|
|
if ($@) {
|
|
$self->{output}->add_option_msg(short_msg => "Cannot decode xml response: $@");
|
|
$self->{output}->option_exit();
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
sub generate_user_defined_id {
|
|
my ($self, %options) = @_;
|
|
|
|
my ($seconds, $microseconds) = Time::HiRes::gettimeofday();
|
|
my $user_defined_id = strftime('%d%B%Y__%H_%M_%S_', localtime($seconds));
|
|
$user_defined_id .= $microseconds;
|
|
|
|
return $user_defined_id;
|
|
}
|
|
|
|
sub time2ms {
|
|
my ($self, %options) = @_;
|
|
|
|
#2019-02-26 10:38:47.407
|
|
return -1 if ($options{time} !~ /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2}).(\d+)/);
|
|
my $dt = DateTime->new(year => $1, month => $2, day => $3, hour => $4, minute => $5, second => $6, nanosecond => $7 * 1000000);
|
|
return $dt->hires_epoch;
|
|
}
|
|
|
|
sub killed_scenario {
|
|
my ($self, %options) = @_;
|
|
|
|
return if (!defined($self->{user_defined_id}));
|
|
$self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_killAll',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => '', warning_status => '', critical_status => '',
|
|
get_param => ['userDefinedId=' . $self->{user_defined_id}]
|
|
);
|
|
}
|
|
|
|
sub cleanup_scenario {
|
|
my ($self, %options) = @_;
|
|
|
|
return if (!defined($self->{user_defined_id}));
|
|
$self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_cleanup',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => '', warning_status => '', critical_status => '',
|
|
get_param => ['userDefinedId=' . $self->{user_defined_id}]
|
|
);
|
|
}
|
|
|
|
sub run_scenario {
|
|
my ($self, %options) = @_;
|
|
|
|
my $user_defined_id = $self->generate_user_defined_id();
|
|
my ($content) = $self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_run',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => $self->{option_results}->{unknown_run_status},
|
|
warning_status => $self->{option_results}->{warning_run_status},
|
|
critical_status => $self->{option_results}->{critical_run_status},
|
|
get_param => [
|
|
'threads=' . $self->{option_results}->{sahi_threads},
|
|
'startWith=' . $self->{option_results}->{sahi_startwith},
|
|
'browserType=' . $self->{option_results}->{sahi_browsertype},
|
|
'suite=' . $self->{option_results}->{sahi_suite},
|
|
'baseURL=' . (defined($self->{option_results}->{sahi_baseurl}) ? $self->{option_results}->{sahi_baseurl} : ''),
|
|
'userDefinedId=' . $user_defined_id,
|
|
]
|
|
);
|
|
|
|
if ($self->{http}->get_code() != 200) {
|
|
$self->{output}->add_option_msg(short_msg => 'run scenario issue:' . $content);
|
|
$self->{output}->option_exit();
|
|
}
|
|
|
|
$self->{user_defined_id} = $user_defined_id;
|
|
}
|
|
|
|
sub check_scenario_status {
|
|
my ($self, %options) = @_;
|
|
|
|
my $content;
|
|
my $retries = 0;
|
|
while (1) {
|
|
($content) = $self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_status',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => '', warning_status => '', critical_status => '',
|
|
get_param => ['userDefinedId=' . $self->{user_defined_id}]
|
|
);
|
|
if ($self->{http}->get_code() != 200) {
|
|
if ($retries == $self->{option_results}->{retries_scenario_status}) {
|
|
$self->{output}->add_option_msg(short_msg => 'check scenario status issue:' . $content);
|
|
$self->{output}->option_exit();
|
|
}
|
|
$retries++;
|
|
} else {
|
|
$retries = 0;
|
|
}
|
|
|
|
# other state: INITIAL, RUNNING
|
|
last if ($content =~ /SUCCESS|FAILURE|ABORTED|SKIPPED|USER_ABORTED/);
|
|
|
|
sleep($self->{option_results}->{interval_scenario_status});
|
|
}
|
|
|
|
my $status = 'UNKNOWN';
|
|
$status = $1 if ($content =~ /(SUCCESS|FAILURE|ABORTED|SKIPPED|USER_ABORTED)/);
|
|
|
|
$self->{global}->{status} = $status;
|
|
}
|
|
|
|
sub get_suite_report {
|
|
my ($self, %options) = @_;
|
|
|
|
my ($content) = $self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_suiteReport',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => '', warning_status => '', critical_status => '',
|
|
get_param => [
|
|
'userDefinedId=' . $self->{user_defined_id},
|
|
'type=xml'
|
|
]
|
|
);
|
|
|
|
if ($self->{http}->get_code() != 200) {
|
|
$self->cleanup_option_exit(short_msg => 'get suite report issue:' . $content);
|
|
}
|
|
|
|
my $response = $self->decode_xml_response(response => $content, ForceArray => ['summary']);
|
|
if (!defined($response->{suite}->{scriptSummaries}->{summary})) {
|
|
$self->{output}->output_add(long_msg => $response, debug => 1);
|
|
$self->cleanup_option_exit(short_msg => 'get suite report issue: unknown response format');
|
|
}
|
|
|
|
# in milliseconds
|
|
$self->{global}->{time_taken} = $response->{suite}->{scriptSummaries}->{summary}->[0]->{TIMETAKEN};
|
|
$self->{global}->{total_steps} = $response->{suite}->{scriptSummaries}->{summary}->[0]->{TOTALSTEPS};
|
|
$self->{global}->{failures} = $response->{suite}->{scriptSummaries}->{summary}->[0]->{FAILURES};
|
|
$self->{global}->{errors} = $response->{suite}->{scriptSummaries}->{summary}->[0]->{ERRORS};
|
|
$self->{script_reportid} = $response->{suite}->{scriptSummaries}->{summary}->[0]->{SCRIPTREPORTID};
|
|
}
|
|
|
|
sub get_script_report {
|
|
my ($self, %options) = @_;
|
|
|
|
my ($content) = $self->{http}->request(
|
|
proto => $self->{option_results}->{sahi_proto},
|
|
port => $self->{option_results}->{sahi_port},
|
|
hostname => $self->{option_results}->{sahi_hostname},
|
|
url_path => $self->{option_results}->{sahi_endpoint} . 'SahiEndPoint_scriptReport',
|
|
timeout => $self->{option_results}->{sahi_http_timeout},
|
|
unknown_status => '', warning_status => '', critical_status => '',
|
|
get_param => [
|
|
'id=' . $options{id},
|
|
'type=xml'
|
|
]
|
|
);
|
|
|
|
if ($self->{http}->get_code() != 200) {
|
|
$self->cleanup_option_exit(short_msg => 'get suite report issue:' . $content);
|
|
}
|
|
|
|
my $response = $self->decode_xml_response(response => $content, ForceArray => ['step']);
|
|
if (!defined($response->{steps}->{step})) {
|
|
$self->{output}->output_add(long_msg => $response, debug => 1);
|
|
$self->cleanup_option_exit(short_msg => 'get script report issue: unknown response format');
|
|
}
|
|
|
|
$self->{steps} = {};
|
|
for (my $i = 0; $i < (scalar(@{$response->{steps}->{step}}) - 1); $i++) {
|
|
my $display = $response->{steps}->{step}->[$i]->{MESSAGETYPE};
|
|
$display .= '.' . $response->{steps}->{step}->[$i]->{STEPMESSAGE}
|
|
if (defined($response->{steps}->{step}->[$i]->{STEPMESSAGE}) && $response->{steps}->{step}->[$i]->{STEPMESSAGE} ne '');
|
|
$display =~ s/\|//g;
|
|
|
|
my $current_time = $self->time2ms(time => $response->{steps}->{step}->[$i]->{MESSAGETIMESTAMP});
|
|
my $next_time = $self->time2ms(time => $response->{steps}->{step}->[$i + 1]->{MESSAGETIMESTAMP});
|
|
my $time_taken = int(($next_time * 1000) - ($current_time * 1000));
|
|
|
|
$self->{steps}->{$i} = {
|
|
step => $i,
|
|
display => $display,
|
|
time_taken => $time_taken,
|
|
};
|
|
$self->{global}->{'step' . $i . '_time'} = $time_taken;
|
|
}
|
|
}
|
|
|
|
sub cleanup_option_exit {
|
|
my ($self, %options) = @_;
|
|
|
|
$self->cleanup_scenario();
|
|
$self->{output}->add_option_msg(short_msg => $options{short_msg});
|
|
$self->{output}->option_exit();
|
|
}
|
|
|
|
sub manage_selection {
|
|
my ($self, %options) = @_;
|
|
|
|
$self->{global} = {};
|
|
$self->run_scenario();
|
|
$self->check_scenario_status();
|
|
|
|
if ($self->{global}->{status} =~ /FAILURE|SUCCESS/) {
|
|
$self->get_suite_report();
|
|
$self->get_script_report(id => $self->{script_reportid});
|
|
}
|
|
|
|
$self->cleanup_scenario();
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 MODE
|
|
|
|
Check scenario execution.
|
|
|
|
=over 8
|
|
|
|
=item B<--sahi-hostname>
|
|
|
|
IP Addr/FQDN of the host
|
|
|
|
=item B<--sahi-port>
|
|
|
|
Port used (Default: 9999)
|
|
|
|
=item B<--sahi-proto>
|
|
|
|
Specify https if needed (Default: 'http')
|
|
|
|
=item B<--sahi-endpoint>
|
|
|
|
Specify endpoint (Default: '/_s_/dyn/')
|
|
|
|
=item B<--sahi-suite>
|
|
|
|
Full path to scenario and scenario name (Required)
|
|
|
|
=item B<--sahi-http-timeout>
|
|
|
|
Timeout for each HTTP requests (Default: 5)
|
|
|
|
=item B<--sahi-threads>
|
|
|
|
Number of simultaneous browser instances that can be executed (Default: 1)
|
|
|
|
=item B<--sahi-startwith>
|
|
|
|
Specify the start mode (Default: BROWSER)
|
|
|
|
=item B<--sahi-browsertype>
|
|
|
|
Browser on which scripts will be executed (Default: chrome)
|
|
|
|
=item B<--sahi-baseurl>
|
|
|
|
Url where the script should start
|
|
|
|
=item B<--timeout>
|
|
|
|
Specify the global script timeout. If timeout is reached, scenario is killed.
|
|
|
|
=item B<--retries-scenario-status>
|
|
|
|
Specify the number of retries to get scenario status (if we fail to get the status).
|
|
|
|
=item B<--interval-scenario-status>
|
|
|
|
Specify time interval to get scenario status in seconds (Default: 10).
|
|
|
|
=item B<--unknown-run-status>
|
|
|
|
Threshold unknown for running scenario rest api response.
|
|
(Default: '%{http_code} < 200 or %{http_code} >= 300')
|
|
|
|
=item B<--warning-run-status>
|
|
|
|
Threshold warning for running scenario rest api response.
|
|
|
|
=item B<--critical-run-status>
|
|
|
|
Threshold critical for running scenario rest api response.
|
|
|
|
=item B<--warning-status>
|
|
|
|
Set warning threshold for scenario status.
|
|
Can used special variables like: %{status}.
|
|
|
|
=item B<--critical-status>
|
|
|
|
Set critical threshold for scenario status (Default: '%{status} ne "SUCCESS"').
|
|
Can used special variables like: %{status}.
|
|
|
|
=item B<--warning-*> B<--critical-*>
|
|
|
|
Set thresholds.
|
|
Can be: 'total-time', 'total-steps', 'failures', 'errors', 'step-time'.
|
|
|
|
=back
|
|
|
|
=cut
|