From 59c5ba9c204046c98bed40ee67729d95ba9cbbdd Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Tue, 24 Mar 2026 16:50:53 +0100 Subject: [PATCH 01/10] Initial version --- .../deb.json | 7 + .../pkg.json | 9 + .../rpm.json | 8 + src/network/paloalto/api/custom/api.pm | 329 ++++++++++++++++++ .../paloalto/api/mode/components/fan.pm | 71 ++++ .../paloalto/api/mode/components/psu.pm | 51 +++ .../api/mode/components/temperature.pm | 72 ++++ .../paloalto/api/mode/components/voltage.pm | 72 ++++ src/network/paloalto/api/mode/environment.pm | 199 +++++++++++ src/network/paloalto/api/mode/ha.pm | 255 ++++++++++++++ src/network/paloalto/api/mode/licenses.pm | 206 +++++++++++ src/network/paloalto/api/mode/system.pm | 216 ++++++++++++ src/network/paloalto/api/mode/tunnel.pm | 185 ++++++++++ src/network/paloalto/api/plugin.pm | 53 +++ tests/network/paloalto/api/authent.robot | 36 ++ tests/network/paloalto/api/environment.robot | 48 +++ tests/network/paloalto/api/fixed_date.pm | 10 + tests/network/paloalto/api/ha.robot | 44 +++ tests/network/paloalto/api/licenses.robot | 49 +++ .../paloalto/api/mockoon-paloalto-api.json | 263 ++++++++++++++ tests/network/paloalto/api/system.robot | 51 +++ tests/network/paloalto/api/tunnel.robot | 43 +++ tests/resources/spellcheck/stopwords.txt | 3 + 23 files changed, 2280 insertions(+) create mode 100644 packaging/centreon-plugin-Network-Paloalto-Api/deb.json create mode 100644 packaging/centreon-plugin-Network-Paloalto-Api/pkg.json create mode 100644 packaging/centreon-plugin-Network-Paloalto-Api/rpm.json create mode 100644 src/network/paloalto/api/custom/api.pm create mode 100644 src/network/paloalto/api/mode/components/fan.pm create mode 100644 src/network/paloalto/api/mode/components/psu.pm create mode 100644 src/network/paloalto/api/mode/components/temperature.pm create mode 100644 src/network/paloalto/api/mode/components/voltage.pm create mode 100644 src/network/paloalto/api/mode/environment.pm create mode 100644 src/network/paloalto/api/mode/ha.pm create mode 100644 src/network/paloalto/api/mode/licenses.pm create mode 100644 src/network/paloalto/api/mode/system.pm create mode 100644 src/network/paloalto/api/mode/tunnel.pm create mode 100644 src/network/paloalto/api/plugin.pm create mode 100644 tests/network/paloalto/api/authent.robot create mode 100644 tests/network/paloalto/api/environment.robot create mode 100644 tests/network/paloalto/api/fixed_date.pm create mode 100644 tests/network/paloalto/api/ha.robot create mode 100644 tests/network/paloalto/api/licenses.robot create mode 100644 tests/network/paloalto/api/mockoon-paloalto-api.json create mode 100644 tests/network/paloalto/api/system.robot create mode 100644 tests/network/paloalto/api/tunnel.robot diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/deb.json b/packaging/centreon-plugin-Network-Paloalto-Api/deb.json new file mode 100644 index 0000000000..439bc815cc --- /dev/null +++ b/packaging/centreon-plugin-Network-Paloalto-Api/deb.json @@ -0,0 +1,7 @@ +{ + "dependencies": [ + "libdatetime-perl", + "libdatetime-format-strptime-perl", + "libxml-simple-perl" + ] +} diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json b/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json new file mode 100644 index 0000000000..fa42427249 --- /dev/null +++ b/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json @@ -0,0 +1,9 @@ +{ + "pkg_name": "centreon-plugin-Network-Paloalto-Api", + "pkg_summary": "Centreon Plugin to monitor Palo Alto Networks firewalls using API XML", + "plugin_name": "centreon_paloalto_api.pl", + "files": [ + "centreon/plugins/script_custom.pm", + "network/paloalto/api/" + ] +} diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/rpm.json b/packaging/centreon-plugin-Network-Paloalto-Api/rpm.json new file mode 100644 index 0000000000..34c3ea44d2 --- /dev/null +++ b/packaging/centreon-plugin-Network-Paloalto-Api/rpm.json @@ -0,0 +1,8 @@ +{ + "dependencies": [ + "perl(DateTime)", + "perl(DateTime::Format::Strptime)", + "perl(XML::Simple)", + "perl(URI::Escape)" + ] +} diff --git a/src/network/paloalto/api/custom/api.pm b/src/network/paloalto/api/custom/api.pm new file mode 100644 index 0000000000..a677d36ea4 --- /dev/null +++ b/src/network/paloalto/api/custom/api.pm @@ -0,0 +1,329 @@ +# +# Copyright 2026-Present 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::paloalto::api::custom::api; + +use strict; +use warnings; +use centreon::plugins::http; +use centreon::plugins::statefile; +use centreon::plugins::constants qw(:values); +use XML::Simple; +use MIME::Base64 qw(encode_base64); +use URI::Escape qw(uri_escape); +use Digest::SHA qw(sha256_hex); +use centreon::plugins::misc qw(is_empty); + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + unless ($options{output}) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3 + } + $options{output}->option_exit(short_msg => "Class Custom: Need to specify 'options' argument.") + unless $options{options}; + + unless ($options{noptions}) { + $options{options}->add_options(arguments => { + 'hostname:s' => { name => 'hostname', default => '' }, + 'port:s' => { name => 'port', default => 443 }, + 'proto:s' => { name => 'proto', default => 'https' }, + 'auth-type:s' => { name => 'auth_type', default => 'api-key' }, + 'api-key:s' => { name => 'api_key', default => '' }, + 'username:s' => { name => 'username', default => '' }, + 'password:s' => { name => 'password', default => '' }, + 'timeout:s' => { name => 'timeout', default => 30 }, + 'unknown-http-status:s' => { name => 'unknown_http_status', default => '%{http_code} < 200 or %{http_code} >= 300' }, + 'warning-http-status:s' => { name => 'warning_http_status', default => '' }, + 'critical-http-status:s' => { name => 'critical_http_status', default => '' } + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{http} = centreon::plugins::http->new(%options, default_backend => 'curl'); + $self->{cache} = 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} = $self->{option_results}->{hostname}; + $self->{port} = $self->{option_results}->{port}; + $self->{proto} = $self->{option_results}->{proto}; + $self->{auth_type} = $self->{option_results}->{auth_type}; + $self->{api_key} = $self->{option_results}->{api_key}; + $self->{username} = $self->{option_results}->{username}; + $self->{password} = $self->{option_results}->{password}; + $self->{timeout} = $self->{option_results}->{timeout}; + $self->{unknown_http_status} = $self->{option_results}->{unknown_http_status}; + $self->{warning_http_status} = $self->{option_results}->{warning_http_status}; + $self->{critical_http_status} = $self->{option_results}->{critical_http_status}; + + $self->{output}->option_exit(short_msg => "Need to specify --hostname option.") + if $self->{hostname} eq ''; + $self->{output}->option_exit(short_msg => "Unknown --auth-type value '$self->{auth_type}' (must be 'api-key' or 'basic').") + if $self->{auth_type} !~ /^(?:api-key|basic)$/; + $self->{output}->option_exit(short_msg => "With --auth-type=api-key: specify --api-key or --username/--password to auto-generate it.") + if $self->{auth_type} eq 'api-key' && $self->{api_key} eq '' && $self->{username} eq ''; + + $self->{output}->option_exit(short_msg => "Need to specify --username/--password options with --auth-type=basic.") + if $self->{auth_type} eq 'basic' && ($self->{username} eq '' || $self->{password} eq ''); + + $self->{cache}->check_options(option_results => $self->{option_results}); + + return 0; +} + +sub get_hostname { + my ($self, %options) = @_; + + return $self->{hostname}; +} + +sub get_port { + my ($self, %options) = @_; + + return $self->{port}; +} + +sub settings { + my ($self, %options) = @_; + + return if $self->{settings_done}; + $self->{option_results}->{$_} = $self->{$_} + foreach qw/hostname port proto timeout/; + $self->{http}->set_options(%{$self->{option_results}}); + $self->{settings_done} = 1; +} + +sub generate_api_key { + my ($self, %options) = @_; + + $self->{output}->output_add(long_msg => "Generating API key for user '$self->{username}'", debug => 1); + + my $content = $self->{http}->request( + url_path => '/api/', + method => 'POST', + get_param => ['type=keygen'], + query_form_post => 'user=' . uri_escape($self->{username}) . '&password=' . uri_escape($self->{password}), + header => ['Content-Type: application/x-www-form-urlencoded'], + unknown_status => '', + warning_status => '', + critical_status => '' + ); + + my $code = $self->{http}->get_code(); + $self->{output}->option_exit(short_msg => sprintf("API key generation failed [code: %s] [message: %s]", $code, $self->{http}->get_message())) + if $code < 200 || $code >= 300; + + my $result = $self->_parse_xml($content); + $self->{output}->option_exit(short_msg => "API key generation response does not contain a key.") + if is_empty($result->{key}); + + $self->{api_key} = $result->{key}; + $self->{cache}->write(data => { + updated => time(), + api_key => $self->{api_key} + }); + $self->{output}->output_add(long_msg => "API key successfully generated and cached", debug => 1); +} + +sub _load_api_key { + my ($self) = @_; + + # Use api-key if it was explicitly provided + return if $self->{api_key} ne ''; + + my $cache_name = 'paloalto_api_' . sha256_hex($self->{hostname} . '_' . $self->{username}); + my $has_cache = $self->{cache}->read(statefile => $cache_name); + + if ($has_cache != BUFFER_CREATION) { + my $cached_key = $self->{cache}->get(name => 'api_key'); + unless (is_empty($cached_key)) { + $self->{api_key} = $cached_key; + $self->{output}->output_add(long_msg => "Using cached API key", debug => 1); + return; + } + } + + $self->generate_api_key(); +} + +sub _build_auth_header { + my ($self) = @_; + + return $self->{auth_type} eq 'api-key' + ? 'X-PAN-KEY: ' . $self->{api_key} + : 'Authorization: Basic ' . encode_base64($self->{username} . ':' . $self->{password}, ''); +} + +sub _http_request { + my ($self, %options) = @_; + + return $self->{http}->request( + url_path => '/api/', + get_param => [ + 'type=' . $options{type}, + 'cmd=' . $options{cmd} + ], + header => [ + $self->_build_auth_header(), + 'Accept: application/xml' + ], + unknown_status => $options{unknown_status} // '', + warning_status => $options{warning_status} // '', + critical_status => $options{critical_status} // '' + ); +} + +sub _parse_xml { + my ($self, $content, %options) = @_; + + $self->{output}->output_add(long_msg => "API response: $content", debug => 1); + + $self->{output}->option_exit( short_msg => "API returns empty content [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']") + if is_empty($content); + + $self->{output}->option_exit(short_msg => "Cannot find XML response in API reply.") + unless $content =~ /(.*<\/response>)/ms; + + my ($xml, $status) = ($1, $2); + $self->{output}->option_exit(short_msg => "API response status: $status") + unless $status eq 'success'; + + my $result; + eval { + $result = XMLin($xml, ForceArray => $options{ForceArray} // [], KeyAttr => []); + }; + $self->{output}->option_exit(short_msg => "Cannot decode XML response: $@") + if $@; + + return $result->{result}; +} + +sub request_api { + my ($self, %options) = @_; + + $self->settings(); + + $self->_load_api_key() if ($self->{auth_type} eq 'api-key'); + + # First attempt without status checking so we can intercept 401/403 + my $content = $self->_http_request(%options); + my $code = $self->{http}->get_code(); + + if ($self->{auth_type} eq 'api-key' && $code =~ /^(?:401|403)$/) { + $self->{output}->output_add(long_msg => "Got HTTP $code, regenerating API key and retrying", debug => 1); + $self->generate_api_key(); + + # Second attempt with status checking enabled + $content = $self->_http_request( + %options, + unknown_status => $self->{unknown_http_status}, + warning_status => $self->{warning_http_status}, + critical_status => $self->{critical_http_status} + ); + } elsif ($code < 200 || $code >= 300) { + $self->{output}->option_exit(short_msg => sprintf("HTTP error [code: %s] [message: %s]", $code, $self->{http}->get_message())); + } + + return $self->_parse_xml($content, %options); +} + +1; + +__END__ + +=head1 NAME + +Palo Alto XML API + +=head1 API OPTIONS + +=over 8 + +=item B<--hostname> + +Hostname or IP address of the Palo Alto device. + +=item B<--port> + +Port used (default: 443). + +=item B<--proto> + +Protocol to use: http or https (default: https). + +=item B<--auth-type> + +Authentication type: C (default) or C. + +=item B<--api-key> + +PAN-OS API key (sent as X-PAN-KEY header). Used with --auth-type=api-key. +If omitted, the key is auto-generated from --username/--password via the C +and cached locally. A 401 or 403 response also triggers automatic key regeneration. + +=item B<--username> + +Username. Required with --auth-type=basic. +Also used with --auth-type=api-key to auto-generate or regenerate the API key. + +=item B<--password> + +Password. + +=item B<--timeout> + +HTTP request timeout in seconds (default: 30). + +=item B<--unknown-http-status> + +Threshold for unknown HTTP status (default: '%{http_code} < 200 or %{http_code} >= 300'). + +=item B<--warning-http-status> + +Threshold for warning HTTP status. + +=item B<--critical-http-status> + +Threshold for critical HTTP status. + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/src/network/paloalto/api/mode/components/fan.pm b/src/network/paloalto/api/mode/components/fan.pm new file mode 100644 index 0000000000..f92e65f0fe --- /dev/null +++ b/src/network/paloalto/api/mode/components/fan.pm @@ -0,0 +1,71 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::components::fan; + +use strict; +use warnings; +use centreon::plugins::misc qw/is_empty/; + +sub check { + my ($self) = @_; + + $self->{output}->output_add(long_msg => "Checking fans"); + $self->{components}->{fan} = { name => 'fans', total => 0, skip => 0 }; + return if $self->check_filter(section => 'fan'); + + foreach my $instance (sort keys %{$self->{data}->{fans}}) { + my $result = $self->{data}->{fans}->{$instance}; + + next if $self->check_filter(section => 'fan', instance => $instance); + next if $self->check_filter(section => 'fan', instance => $result->{description}); + $self->{components}->{fan}->{total}++; + + $self->{output}->output_add(long_msg => sprintf("Fan '%s' alarm is '%s' [instance: %s, rpm: %s]", + $result->{description}, $result->{alarm}, + $instance, $result->{rpm})); + + my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Fan '%s' alarm is %s", $result->{description}, $alarm_status)) + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + + unless (is_empty($result->{rpm})){ + my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( + section => 'fan', + instance => $instance, + value => $result->{rpm} + ); + $self->{output}->output_add(severity => $exit, short_msg => sprintf("Fan '%s' rpm is %s", $result->{description}, $result->{rpm})) + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + + my $label = $result->{description} . '#hardware.fan.speed.rpm'; + $self->{output}->perfdata_add( + label => $label, + unit => 'rpm', + value => $result->{rpm}, + warning => $warn, + critical => $crit, + min => $result->{min} + ); + } + } +} + +1; diff --git a/src/network/paloalto/api/mode/components/psu.pm b/src/network/paloalto/api/mode/components/psu.pm new file mode 100644 index 0000000000..21aeab0f12 --- /dev/null +++ b/src/network/paloalto/api/mode/components/psu.pm @@ -0,0 +1,51 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::components::psu; + +use strict; +use warnings; +use centreon::plugins::misc qw/is_empty/; + +sub check { + my ($self) = @_; + + $self->{output}->output_add(long_msg => "Checking power supplies"); + $self->{components}->{psu} = { name => 'power supplies', total => 0, skip => 0 }; + return if $self->check_filter(section => 'psu'); + + foreach my $instance (sort keys %{$self->{data}->{psus}}) { + my $result = $self->{data}->{psus}->{$instance}; + + next if $self->check_filter(section => 'psu', instance => $instance); + next if $self->check_filter(section => 'psu', instance => $result->{description}); + $self->{components}->{psu}->{total}++; + + $self->{output}->output_add(long_msg => sprintf("Power supply '%s' alarm is '%s' [instance: %s, inserted: %s]", + $result->{description}, $result->{alarm}, + $instance, $result->{inserted})); + + my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Power supply '%s' alarm is %s", $result->{description}, $alarm_status)) + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + } +} + +1; diff --git a/src/network/paloalto/api/mode/components/temperature.pm b/src/network/paloalto/api/mode/components/temperature.pm new file mode 100644 index 0000000000..1a1f8af614 --- /dev/null +++ b/src/network/paloalto/api/mode/components/temperature.pm @@ -0,0 +1,72 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::components::temperature; + +use strict; +use warnings; +use centreon::plugins::misc qw/is_empty/; + +sub check { + my ($self) = @_; + + $self->{output}->output_add(long_msg => "Checking temperatures"); + $self->{components}->{temperature} = { name => 'temperatures', total => 0, skip => 0 }; + return if $self->check_filter(section => 'temperature'); + + foreach my $instance (sort keys %{$self->{data}->{temperatures}}) { + my $result = $self->{data}->{temperatures}->{$instance}; + + next if $self->check_filter(section => 'temperature', instance => $instance); + next if $self->check_filter(section => 'temperature', instance => $result->{description}); + $self->{components}->{temperature}->{total}++; + + $self->{output}->output_add(long_msg => sprintf("Temperature '%s' alarm is '%s' [instance: %s, value: %s C]", + $result->{description}, $result->{alarm}, + $instance, $result->{value})); + + my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Temperature '%s' alarm is %s", $result->{description}, $alarm_status)) + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + + unless (is_empty($result->{value})) { + my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( + section => 'temperature', + instance => $instance, + value => $result->{value} + ); + $self->{output}->output_add(severity => $exit, short_msg => sprintf("Temperature '%s' value is %s C", $result->{description}, $result->{value})) + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + + my $label = $result->{description} . '#hardware.temperature.celsius'; + $self->{output}->perfdata_add( + label => $label, + unit => 'C', + value => $result->{value}, + warning => $warn, + critical => $crit, + min => $result->{min}, + max => $result->{max} + ); + } + } +} + +1; diff --git a/src/network/paloalto/api/mode/components/voltage.pm b/src/network/paloalto/api/mode/components/voltage.pm new file mode 100644 index 0000000000..9347ba0208 --- /dev/null +++ b/src/network/paloalto/api/mode/components/voltage.pm @@ -0,0 +1,72 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::components::voltage; + +use strict; +use warnings; +use centreon::plugins::misc qw/is_empty/; + +sub check { + my ($self) = @_; + + $self->{output}->output_add(long_msg => "Checking voltages"); + $self->{components}->{voltage} = { name => 'voltages', total => 0, skip => 0 }; + return if $self->check_filter(section => 'voltage'); + + foreach my $instance (sort keys %{$self->{data}->{voltages}}) { + my $result = $self->{data}->{voltages}->{$instance}; + + next if $self->check_filter(section => 'voltage', instance => $instance); + next if $self->check_filter(section => 'voltage', instance => $result->{description}); + $self->{components}->{voltage}->{total}++; + + $self->{output}->output_add(long_msg => sprintf("Voltage '%s' alarm is '%s' [instance: %s, value: %s V]", + $result->{description}, $result->{alarm}, + $instance, $result->{value})); + + my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Voltage '%s' alarm is %s", $result->{description}, $alarm_status)) + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + + unless (is_empty($result->{value})) { + my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( + section => 'voltage', + instance => $instance, + value => $result->{value} + ); + $self->{output}->output_add(severity => $exit, short_msg => sprintf("Voltage '%s' value is %s V", $result->{description}, $result->{value})) + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + + my $label = $result->{description} . '#hardware.voltage.volt'; + $self->{output}->perfdata_add( + label => $label, + unit => 'V', + value => $result->{value}, + warning => $warn, + critical => $crit, + min => $result->{min}, + max => $result->{max} + ); + } + } +} + +1; diff --git a/src/network/paloalto/api/mode/environment.pm b/src/network/paloalto/api/mode/environment.pm new file mode 100644 index 0000000000..38f05593d4 --- /dev/null +++ b/src/network/paloalto/api/mode/environment.pm @@ -0,0 +1,199 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::environment; + +use base qw(centreon::plugins::templates::hardware); + +use strict; +use warnings; + +sub set_system { + my ($self, %options) = @_; + + $self->{regexp_threshold_numeric_check_section_option} = '^(?:temperature|fan|voltage)$'; + + $self->{cb_hook2} = 'api_execute'; + + $self->{thresholds} = { + default => [ + ['false', 'OK'], + ['.*', 'CRITICAL'] + ] + }; + + $self->{components_exec_load} = 0; + + $self->{components_path} = 'network::paloalto::api::mode::components'; + $self->{components_module} = ['temperature', 'fan', 'voltage', 'psu']; +} + +sub api_execute { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api( + type => 'op', + cmd => '', + ForceArray => ['entry'] + ); + + # Structure the API response for the component checks + # Data will be organized as: $self->{data}->{component_type}->{instance} = { ... } + $self->{data} = {}; + + # Process temperature data + if (defined($result->{thermal}->{Slot1}->{entry})) { + my $entries = $result->{thermal}->{Slot1}->{entry}; + $entries = [$entries] unless ref($entries) eq 'ARRAY'; + + my $temp_idx = 0; + foreach my $entry (@$entries) { + $temp_idx++; + my $instance = "thermal_slot" . ($entry->{slot} // 1) . "_index" . $temp_idx; + $self->{data}->{temperatures}->{$instance} = { + description => $entry->{description} // "Temperature $temp_idx", + value => $entry->{DegreesC} // 0, + min => $entry->{min} // 0, + max => $entry->{max} // 100, + alarm => $entry->{alarm} // 'False' + }; + } + } + + # Process fan data + if (defined($result->{fan}->{Slot1}->{entry})) { + my $entries = $result->{fan}->{Slot1}->{entry}; + $entries = [$entries] unless ref($entries) eq 'ARRAY'; + + my $fan_idx = 0; + foreach my $entry (@$entries) { + $fan_idx++; + my $instance = "fan_slot" . ($entry->{slot} // 1) . "_index" . $fan_idx; + $self->{data}->{fans}->{$instance} = { + description => $entry->{description} // "Fan $fan_idx", + rpm => $entry->{RPMs} // 0, + min => $entry->{min} // 0, + alarm => $entry->{alarm} // 'False' + }; + } + } + + # Process voltage (power) data + if (defined($result->{power}->{Slot1}->{entry})) { + my $entries = $result->{power}->{Slot1}->{entry}; + $entries = [$entries] unless ref($entries) eq 'ARRAY'; + + my $voltage_idx = 0; + foreach my $entry (@$entries) { + $voltage_idx++; + my $instance = "voltage_slot" . ($entry->{slot} // 1) . "_index" . $voltage_idx; + $self->{data}->{voltages}->{$instance} = { + description => $entry->{description} // "Voltage $voltage_idx", + value => $entry->{Volts} // 0, + min => $entry->{min} // 0, + max => $entry->{max} // 5, + alarm => $entry->{alarm} // 'False' + }; + } + } + + # Process PSU (power supply) data + if (defined($result->{'power-supply'}->{Slot1}->{entry})) { + my $entries = $result->{'power-supply'}->{Slot1}->{entry}; + $entries = [$entries] unless ref($entries) eq 'ARRAY'; + + my $psu_idx = 0; + foreach my $entry (@$entries) { + $psu_idx++; + my $instance = "psu_slot" . ($entry->{slot} // 1) . "_index" . $psu_idx; + $self->{data}->{psus}->{$instance} = { + description => $entry->{description} // "PSU $psu_idx", + inserted => $entry->{Inserted} // 'False', + alarm => $entry->{alarm} // 'False' + }; + } + } +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, no_absent => 1, force_new_perfdata => 1); + bless $self, $class; + + $options{options}->add_options(arguments => {}); + + return $self; +} + +1; + +__END__ + +=head1 MODE + +Check Palo Alto environment sensors (temperatures, fans, voltages, power supplies). + +=over 8 + +=item B<--component> + +Which component to check (default: '.*'). +Can be: 'psu', 'temperature', 'fan', 'voltage'. + +=item B<--filter> + +Exclude the items given as a comma-separated list (example: --filter=temperature). +You can also exclude items from specific instances: --filter=C + +=item B<--absent-problem> + +Return an error if a component is not 'present' (default is skipping). +It can be set globally or for a specific instance: --absent-problem='component_name' or --absent-problem='component_name,instance_value'. + +=item B<--no-component> + +Define the expected status if no components are found (default: critical). + +=item B<--threshold-overload> + +Use this option to override the status returned by the plugin when the status label matches a regular expression (syntax: section,[instance,]status,regexp). +Example: --threshold-overload='psu,ok,true' + +=item B<--warning> + +Set warning threshold for 'temperature', 'fan', 'voltage' (syntax: type,regexp,threshold) +Example: --warning='temperature,.*,50' --warning='fan,.*,2500' + +=item B<--critical> + +Set critical threshold for 'temperature', 'fan', 'voltage' (syntax: type,regexp,threshold) +Example: --critical='temperature,.*,70' --critical='fan,.*,1000' + +=item B<--warning-count-*> + +Define the warning threshold for the number of components of one type (replace '*' with the component type). + +=item B<--critical-count-*> + +Define the critical threshold for the number of components of one type (replace '*' with the component type). + +=back + +=cut diff --git a/src/network/paloalto/api/mode/ha.pm b/src/network/paloalto/api/mode/ha.pm new file mode 100644 index 0000000000..f691b00a7d --- /dev/null +++ b/src/network/paloalto/api/mode/ha.pm @@ -0,0 +1,255 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::ha; + +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::constants qw(:counters); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + bless $self, $class; + + return $self; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'ha', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_ha_output' } + ]; + + $self->{maps_counters}->{ha} = [ + { + label => 'local-state', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'local_state_priority' } ], + output_template => '%s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'peer-state', + type => COUNTER_KIND_TEXT, + warning_default => '%{peer_state_priority} =~ /suspicious/i', + critical_default => '%{peer_state_priority} !~ /active|passive/i || %{peer_state_priority} !~ /up/i', + set => { + key_values => [ { name => 'peer_state_priority' } ], + output_template => '%s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'state-sync', + type => COUNTER_KIND_TEXT, + critical_default => '%{state_sync} !~ /^synchronized|complete$/i', + set => { + key_values => [ { name => 'state_sync' } ], + output_template => 'state sync: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'ha1-link-status', + type => COUNTER_KIND_TEXT, + critical_default => '%{ha1_status} ne "up"', + set => { + key_values => [ { name => 'ha1_status' } ], + output_template => 'HA1 link: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'ha2-link-status', + type => COUNTER_KIND_TEXT, + critical_default => '%{ha2_status} ne "up"', + set => { + key_values => [ { name => 'ha2_status' } ], + output_template => 'HA2 link: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'ha-mode', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'ha_mode' } ], + output_template => 'HA mode: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'build-compat', + type => COUNTER_KIND_TEXT, + warning_default => '%{build_compat} ne "Match"', + set => { + key_values => [ { name => 'build_compat' } ], + output_template => 'build compatibility: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + } + ]; +} + +sub prefix_ha_output { + my ($self, %options) = @_; + my $model = $options{instance_value}->{platform_model} // 'HA'; + return "$model status: "; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api( + type => 'op', + cmd => '' + ); + + my $group = $result->{group} // {}; + my $local = $group->{'local-info'} // {}; + my $peer = $group->{'peer-info'} // {}; + + my $local_state = lc($local->{state} //''); + my $peer_state = lc($peer->{state} // ''); + my $ha_mode = lc($group->{mode} // ''); + my $platform_model = $local->{'platform-model'} // ''; + + $self->{ha} = { + platform_model => $platform_model, + local_state_priority => "local state: $local_state (priority: " . $local->{priority} . ")", + peer_state_priority => "peer state: $peer_state (priority: " . $peer->{priority} . ", conn: " . lc($peer->{'conn-status'}) . ")", + state_sync => lc($group->{'running-sync'}), + ha1_status => lc($peer->{'conn-ha1'}->{'conn-status'}), + ha2_status => lc($peer->{'conn-ha2'}->{'conn-status'}), + ha_mode => $ha_mode, + build_compat => $peer->{'build-compat'} || $local->{'build-compat'} + }; +} + +1; + +__END__ + +=head1 MODE + +Check Palo Alto High Availability (HA) status. + +=over 8 + +=item B<--unknown-local-state> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{local_state}, %{local_priority} + +=item B<--warning-local-state> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{local_state}, %{local_priority} + +=item B<--critical-local-state> + +Define the conditions to match for the status to be CRITICAL (default: '%{local_state} !~ /^(?:active|passive)$/i'). +You can use the following variables: %{local_state}, %{local_priority} + +=item B<--unknown-peer-state> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} + +=item B<--warning-peer-state> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} + +=item B<--critical-peer-state> + +Define the conditions to match for the status to be CRITICAL (default: '%{peer_state} !~ /^(?:active|passive|unknown)$/i || %{peer_conn_status} ne "up"'). +You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} + +=item B<--unknown-state-sync> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{state_sync} + +=item B<--warning-state-sync> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{state_sync} + +=item B<--critical-state-sync> + +Define the conditions to match for the status to be CRITICAL (default: '%{state_sync} !~ /^synchronized|complete$/i'). +You can use the following variables: %{state_sync} + +=item B<--unknown-ha1-link-status> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{ha1_status} + +=item B<--warning-ha1-link-status> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{ha1_status} + +=item B<--critical-ha1-link-status> + +Define the conditions to match for the status to be CRITICAL (default: '%{ha1_status} ne "up"'). +You can use the following variables: %{ha1_status} + +=item B<--unknown-ha2-link-status> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{ha2_status} + +=item B<--warning-ha2-link-status> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{ha2_status} + +=item B<--critical-ha2-link-status> + +Define the conditions to match for the status to be CRITICAL (default: '%{ha2_status} ne "up"'). +You can use the following variables: %{ha2_status} + +=item B<--unknown-build-compat> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{build_compat} + +=item B<--warning-build-compat> + +Define the conditions to match for the status to be WARNING (default: '%{build_compat} ne "Match"'). +You can use the following variables: %{build_compat} + +=item B<--critical-build-compat> + +Define the conditions to match for the status to be CRITICAL. +You can use the following variables: %{build_compat} + +=back + +=cut diff --git a/src/network/paloalto/api/mode/licenses.pm b/src/network/paloalto/api/mode/licenses.pm new file mode 100644 index 0000000000..a714d1bfa8 --- /dev/null +++ b/src/network/paloalto/api/mode/licenses.pm @@ -0,0 +1,206 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::licenses; + +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::constants qw(:counters); +use centreon::plugins::misc qw(is_excluded); +use DateTime; +use DateTime::Format::Strptime; + +sub custom_expiration_output { + my ($self, %options) = @_; + my $days = $self->{result_values}->{days_left}; + return 'never expire' if $days && $days == -1; + return ($days // 0) . ' days left'; +} + +sub prefix_license_output { + my ($self, %options) = @_; + return sprintf("license '%s' ", $options{instance_value}->{feature}); +} + +sub prefix_global_output { + my ($self, %options) = @_; + return 'Licenses '; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_global_output' }, + { name => 'licenses', type => COUNTER_TYPE_INSTANCE, cb_prefix_output => 'prefix_license_output', message_multiple => 'All licenses are ok' } + ]; + + $self->{maps_counters}->{global} = [ + { + label => 'licenses-count', + nlabel => 'licenses.count', + set => { + key_values => [ { name => 'licenses_count' } ], + output_template => 'count: %s', + perfdatas => [ + { template => '%s', min => 0 } + ] + } + } + ]; + + $self->{maps_counters}->{licenses} = [ + { + label => 'status', + type => COUNTER_KIND_TEXT, + critical_default => '%{expired} =~ /yes/i', + set => { + key_values => [ { name => 'expired' }, { name => 'feature' } ], + output_template => 'expired: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'expiration-days', + nlabel => 'license.empiration.days', + critical_default => '@0', + set => { + key_values => [ { name => 'days_left' }, { name => 'feature' } ], + closure_custom_output => \&custom_expiration_output, + perfdatas => [ + { template => '%s', unit => 'd', min => -1, + label_extra_instance => 1, instance_use => 'feature' } + ] + } + } + ]; +} + +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 => { + 'include-license-name:s' => { name => 'include_license_name', default => '' }, + 'exclude-license-name:s' => { name => 'exclude_license_name', default => '' } + }); + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api( + type => 'op', + cmd => '', + ForceArray => ['entry'] + ); + + $self->{licenses} = {}; + $self->{global} = { licenses_count => 0 }; + + my $parser = DateTime::Format::Strptime->new( + pattern => '%B %d, %Y', + on_error => 'undef' + ); + + return unless defined($result->{licenses}); + + foreach my $entry (@{$result->{licenses}->{entry}}) { + my $feature = $entry->{feature} // ''; + next if is_excluded($feature, $self->{option_results}->{include_license_name}, $self->{option_results}->{exclude_license_name}); + + my $days_left = -1; + my $expires = $entry->{expires}; + + if (defined($expires) && $expires ne 'Never') { + my $exp_date = $parser->parse_datetime($expires); + if ($exp_date) { + my $now = DateTime->now(time_zone => 'UTC'); + $days_left = int(($exp_date->epoch() - $now->epoch()) / 86400); + $days_left = 0 if $days_left < 0; + } + } + $self->{licenses}->{$feature} = { + feature => $feature, + expired => lc($entry->{expired}), + days_left => $days_left + }; + $self->{global}->{licenses_count}++; + } +} + +1; + +__END__ + +=head1 MODE + +Check Palo Alto licenses status and expiration. + +=over 8 + +=item B<--include-license-name> + +Include license names (regexp). + +=item B<--exclude-license-name> + +Exclude license names (regexp). + +=item B<--unknown-status> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{expired} + +=item B<--warning-status> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{expired} + +=item B<--critical-status> + +Define the conditions to match for the status to be CRITICAL (default: '%{expired} =~ /yes/i'). +You can use the following variables: %{expired} + +=item B<--warning-expiration-days> + +Warning threshold in days before expiration. + +=item B<--critical-expiration-days> + +Critical threshold in days before expiration (default: '@0'). + +=item B<--warning-licenses-count> + +Warning threshold for licenses count. + +=item B<--critical-licenses-count> + +Critical threshold for licenses count. + +=back + +=cut diff --git a/src/network/paloalto/api/mode/system.pm b/src/network/paloalto/api/mode/system.pm new file mode 100644 index 0000000000..695b286575 --- /dev/null +++ b/src/network/paloalto/api/mode/system.pm @@ -0,0 +1,216 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::system; + +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::constants qw(:counters); + +sub prefix_global_output { + my ($self, %options) = @_; + return 'System '; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_global_output' } + ]; + + $self->{maps_counters}->{global} = [ + { + label => 'uptime', + nlabel => 'system.uptime.seconds', + set => { + key_values => [ { name => 'uptime' } ], + output_template => 'uptime: %s seconds', + perfdatas => [ + { template => '%s', unit => 's', min => 0 } + ] + } + }, + { + label => 'certificate-status', + type => COUNTER_KIND_TEXT, + critical_default => '%{cert_status} !~ /Valid/i', + set => { + key_values => [ { name => 'cert_status' } ], + output_template => 'certificate status: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'operational-mode', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'operational_mode' } ], + output_template => 'operational mode: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'software-version', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'sw_version' } ], + output_template => 'software version: %s', + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + }, + { + label => 'wildfire-mode', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'wildfire_mode' } ], + output_template => 'WildFire mode: %s', + 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; + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api( + type => 'op', + cmd => '' + ); + + $self->{global} = { + uptime => 0, + cert_status => 'Unknown', + operational_mode => 'Unknown', + sw_version => 'Unknown', + wildfire_mode => 'Unknown' + }; + + return unless defined($result->{system}); + + my $system = $result->{system}; + + # Parse uptime: "X days, HH:MM:SS" format + if (defined($system->{uptime})) { + my $uptime_str = $system->{uptime}; + my $uptime_seconds = 0; + + if ($uptime_str =~ /^(\d+)\s+days?,\s+(\d+):(\d+):(\d+)$/) { + my ($days, $hours, $minutes, $seconds) = ($1, $2, $3, $4); + $uptime_seconds = $days * 86400 + $hours * 3600 + $minutes * 60 + $seconds; + } elsif ($uptime_str =~ /^(\d+):(\d+):(\d+)$/) { + my ($hours, $minutes, $seconds) = ($1, $2, $3); + $uptime_seconds = $hours * 3600 + $minutes * 60 + $seconds; + } + + $self->{global}->{uptime} = $uptime_seconds; + } + + $self->{global}->{cert_status} = $system->{'device-certificate-status'} + if $system->{'device-certificate-status'}; + + $self->{global}->{operational_mode} = $system->{'operational-mode'} + if $system->{'operational-mode'}; + + $self->{global}->{sw_version} = $system->{'sw-version'} + if $system->{'sw-version'}; + + $self->{global}->{wildfire_mode} = $system->{'wildfire-rt'} + if $system->{'wildfire-rt'}; +} + +1; + +__END__ + +=head1 MODE + +Check Palo Alto system information and status. + +=over 8 + +=item B<--warning-uptime> + +Warning threshold for uptime in seconds. + +=item B<--critical-uptime> + +Critical threshold for uptime in seconds. + +=item B<--unknown-certificate-status> + +Define the conditions to match for the status to be UNKNOWN. +You can use the following variables: %{cert_status} + +=item B<--warning-certificate-status> + +Define the conditions to match for the status to be WARNING. +You can use the following variables: %{cert_status} + +=item B<--critical-certificate-status> + +Define the conditions to match for the status to be CRITICAL (default: '%{cert_status} !~ /Valid/i'). +You can use the following variables: %{cert_status} + +=back + +=head1 AVAILABLE COUNTERS + +=over 8 + +=item B + +Total number of active system counters. + +=item B + +System uptime in seconds. + +=item B + +Device certificate status (Valid/Invalid/etc). + +=item B + +Current operational mode (normal/maintenance/etc). + +=item B + +Software version string. + +=item B + +WildFire mode status (Enabled/Disabled). + +=back + +=cut diff --git a/src/network/paloalto/api/mode/tunnel.pm b/src/network/paloalto/api/mode/tunnel.pm new file mode 100644 index 0000000000..7f55e829de --- /dev/null +++ b/src/network/paloalto/api/mode/tunnel.pm @@ -0,0 +1,185 @@ +# +# Copyright 2026-Present 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::paloalto::api::mode::tunnel; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::constants qw(:counters); +use centreon::plugins::misc qw(is_excluded); + +sub prefix_tunnel_output { + my ($self, %options) = @_; + return sprintf("tunnel '%s' ", $options{instance_value}->{name}); +} + +sub prefix_global_output { + my ($self, %options) = @_; + return 'Tunnels '; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_global_output' }, + { name => 'tunnels', type => COUNTER_TYPE_INSTANCE, cb_prefix_output => 'prefix_tunnel_output', message_multiple => 'All tunnels are ok' } + ]; + + $self->{maps_counters}->{global} = [ + { + label => 'tunnels-count', + nlabel => 'tunnels.count', + set => { + key_values => [ { name => 'tunnels_count' } ], + output_template => 'count: %s', + perfdatas => [ + { template => '%s', min => 0 } + ] + } + } + ]; + + $self->{maps_counters}->{tunnels} = [ + { + label => 'remain-time', + nlabel => 'tunnel.remain.seconds', + set => { + key_values => [ { name => 'remain' }, { name => 'name' } ], + output_template => 'remain: %s seconds', + perfdatas => [ + { template => '%s', unit => 's', min => 0, + label_extra_instance => 1, instance_use => 'name' } + ] + } + }, + { + label => 'encryption', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'enc' }, { name => 'name' } ], + output_template => 'encryption: %s' + } + }, + { + label => 'gateway', + type => COUNTER_KIND_TEXT, + set => { + key_values => [ { name => 'gateway' }, { name => 'name' } ], + output_template => 'gateway: %s' + } + } + ]; +} + +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 => { + 'include-tunnel-name:s' => { name => 'include_tunnel_name', default => '' }, + 'exclude-tunnel-name:s' => { name => 'exclude_tunnel_name', default => '' }, + 'include-gateway-name:s' => { name => 'include_gateway_name', default => '' }, + 'exclude-gateway-name:s' => { name => 'exclude_gateway_name', default => '' } + }); + + return $self; +} + +sub manage_selection { + my ($self, %options) = @_; + + my $result = $options{custom}->request_api( + type => 'op', + cmd => '', + ForceArray => ['entry'] + ); + + $self->{tunnels} = {}; + $self->{global} = { tunnels_count => 0 }; + + return unless ref $result->{entries} eq 'HASH'; + + foreach my $entry (@{$result->{entries}->{entry}}) { + my $name = $entry->{name} // ''; + my $gateway = $entry->{gateway} // ''; + + next if is_excluded($name, $self->{option_results}->{include_tunnel_name}, $self->{option_results}->{exclude_tunnel_name}); + next if is_excluded($gateway, $self->{option_results}->{include_gateway_name}, $self->{option_results}->{exclude_gateway_name}); + + $self->{tunnels}->{$name} = { + name => $name, + gateway => $gateway, + enc => $entry->{enc} // 'Unknown', + remain => $entry->{remain} // 0, + proto => $entry->{proto} // 'Unknown' + }; + $self->{global}->{tunnels_count}++; + } +} + +1; + +__END__ + +=head1 MODE + +Check Palo Alto IPsec VPN tunnels status and lifetime. + +=over 8 + +=item B<--include-tunnel-name> + +Include tunnel names (regexp). + +=item B<--exclude-tunnel-name> + +Exclude tunnel names (regexp). + +=item B<--include-gateway-name> + +Include gateway names (regexp). + +=item B<--exclude-gateway-name> + +Exclude gateway names (regexp). + +=item B<--warning-tunnels-count> + +Warning threshold for tunnels count. + +=item B<--critical-tunnels-count> + +Critical threshold for tunnels count. + +=item B<--warning-remain-time> + +Warning threshold for tunnel remain time in seconds. + +=item B<--critical-remain-time> + +Critical threshold for tunnel remain time in seconds. + +=back + +=cut diff --git a/src/network/paloalto/api/plugin.pm b/src/network/paloalto/api/plugin.pm new file mode 100644 index 0000000000..a22bcd35d8 --- /dev/null +++ b/src/network/paloalto/api/plugin.pm @@ -0,0 +1,53 @@ +# +# Copyright 2026-Present 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::paloalto::api::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} = { + 'environment' => 'network::paloalto::api::mode::environment', + 'ha' => 'network::paloalto::api::mode::ha', + 'licenses' => 'network::paloalto::api::mode::licenses', + 'system' => 'network::paloalto::api::mode::system', + 'tunnel' => 'network::paloalto::api::mode::tunnel' + }; + + $self->{custom_modes}->{api} = 'network::paloalto::api::custom::api'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Palo Alto devices using the PAN-OS XML API. + +=cut diff --git a/tests/network/paloalto/api/authent.robot b/tests/network/paloalto/api/authent.robot new file mode 100644 index 0000000000..e28f5d9240 --- /dev/null +++ b/tests/network/paloalto/api/authent.robot @@ -0,0 +1,36 @@ +*** Settings *** +Documentation Check PaloAlto authentication (api-key or basic). + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=system + +*** Test Cases *** +paloalto-environment ${tc} + [Tags] network paloalto api environment + ${command} Catenate + ... ${CMD} + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} UNKNOWN: With --auth-type=api-key: specify --api-key or --username/--password to auto-generate it. + ... 2 --auth-type=api-key UNKNOWN: With --auth-type=api-key: specify --api-key or --username/--password to auto-generate it. + ... 3 --auth-type=api-key --username=AA --password=BB OK: System uptime: 8552549 seconds, certificate status: Valid, operational mode: normal, software version: 10.1.12, WildFire mode: Disabled | 'system.uptime.seconds'=8552549s;;;0; + ... 4 --auth-type=basic UNKNOWN: Need to specify --username/--password options with --auth-type=basic. + ... 5 --auth-type=basic --username=AA --password=BB OK: System uptime: 8552549 seconds, certificate status: Valid, operational mode: normal, software version: 10.1.12, WildFire mode: Disabled | 'system.uptime.seconds'=8552549s;;;0; diff --git a/tests/network/paloalto/api/environment.robot b/tests/network/paloalto/api/environment.robot new file mode 100644 index 0000000000..18ad755f20 --- /dev/null +++ b/tests/network/paloalto/api/environment.robot @@ -0,0 +1,48 @@ +*** Settings *** +Documentation Check PaloAlto environment (temperatures, fans, voltages, PSUs). + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=environment + +*** Test Cases *** +paloalto-environment ${tc} + [Tags] network paloalto api environment + ${command} Catenate + ... ${CMD} + ... --auth-type=api-key + ... --api-key=D@pAs$W@rD + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} OK: All 20 components are ok [3/3 fans, 2/2 power supplies, 5/5 temperatures, 10/10 voltages]. | 'Temperature near CPLD (inlet)#hardware.temperature.celsius'=40.9C;;;0.0;60.0 'Temperature near Cavium (outlet)#hardware.temperature.celsius'=51.6C;;;0.0;60.0 'Temperature near Management Port (inlet)#hardware.temperature.celsius'=38.0C;;;0.0;60.0 'Temperature near Switch (midboard)#hardware.temperature.celsius'=45.0C;;;0.0;60.0 'Temperature @ Cavium Core#hardware.temperature.celsius'=53.175C;;;0.0;85.0 'Fan #1 RPM#hardware.fan.speed.rpm'=9157rpm;;;2500; 'Fan #2 RPM#hardware.fan.speed.rpm'=9557rpm;;;2500; 'Fan #3 RPM#hardware.fan.speed.rpm'=9418rpm;;;2500; '0.85V Power Rail#hardware.voltage.volt'=0.860666666667V;;;0.76;0.94 '3.3V SD Power Rail#hardware.voltage.volt'=3.332V;;;2.97;3.63 '0.9V Power Rail#hardware.voltage.volt'=0.904V;;;0.81;0.99 '1.0V Power Rail#hardware.voltage.volt'=1.006V;;;0.9;1.1 '1.1V Power Rail#hardware.voltage.volt'=1.09466666667V;;;0.99;1.21 '1.2V Power Rail#hardware.voltage.volt'=1.224V;;;1.08;1.32 '1.5V Power Rail#hardware.voltage.volt'=1.51066666667V;;;1.35;1.65 '1.8V Power Rail#hardware.voltage.volt'=1.812V;;;1.62;1.98 '2.5V Power Rail#hardware.voltage.volt'=2.504V;;;2.25;2.75 '3.3V Power Rail#hardware.voltage.volt'=3.332V;;;2.97;3.63 'hardware.fan.count'=3;;;; 'hardware.psu.count'=2;;;; 'hardware.temperature.count'=5;;;; 'hardware.voltage.count'=10;;;; + ... 2 --component=fan OK: All 3 components are ok [3/3 fans]. | 'Fan #1 RPM#hardware.fan.speed.rpm'=9157rpm;;;2500; 'Fan #2 RPM#hardware.fan.speed.rpm'=9557rpm;;;2500; 'Fan #3 RPM#hardware.fan.speed.rpm'=9418rpm;;;2500; 'hardware.fan.count'=3;;;; + ... 3 --component=fan --filter='fan,Fan #2 RPM' --filter='fan,Fan #3 RPM' OK: All 1 components are ok [1/1 fans]. | 'Fan #1 RPM#hardware.fan.speed.rpm'=9157rpm;;;2500; 'hardware.fan.count'=1;;;; + ... 4 --component=fan --warning='fan,.*,9100' WARNING: Fan 'Fan #1 RPM' rpm is 9157 - Fan 'Fan #2 RPM' rpm is 9557 - Fan 'Fan #3 RPM' rpm is 9418 | 'Fan #1 RPM#hardware.fan.speed.rpm'=9157rpm;0:9100;;2500; 'Fan #2 RPM#hardware.fan.speed.rpm'=9557rpm;0:9100;;2500; 'Fan #3 RPM#hardware.fan.speed.rpm'=9418rpm;0:9100;;2500; 'hardware.fan.count'=3;;;; + ... 5 --component=fan --critical='fan,.*,9100' CRITICAL: Fan 'Fan #1 RPM' rpm is 9157 - Fan 'Fan #2 RPM' rpm is 9557 - Fan 'Fan #3 RPM' rpm is 9418 | 'Fan #1 RPM#hardware.fan.speed.rpm'=9157rpm;;0:9100;2500; 'Fan #2 RPM#hardware.fan.speed.rpm'=9557rpm;;0:9100;2500; 'Fan #3 RPM#hardware.fan.speed.rpm'=9418rpm;;0:9100;2500; 'hardware.fan.count'=3;;;; + ... 6 --component=psu OK: All 2 components are ok [2/2 power supplies]. | 'hardware.psu.count'=2;;;; + ... 7 --component=psu --filter='psu,Supply #2' OK: All 1 components are ok [1/1 power supplies]. | 'hardware.psu.count'=1;;;; + ... 8 --component=voltage OK: All 10 components are ok [10/10 voltages]. | '0.85V Power Rail#hardware.voltage.volt'=0.860666666667V;;;0.76;0.94 '3.3V SD Power Rail#hardware.voltage.volt'=3.332V;;;2.97;3.63 '0.9V Power Rail#hardware.voltage.volt'=0.904V;;;0.81;0.99 '1.0V Power Rail#hardware.voltage.volt'=1.006V;;;0.9;1.1 '1.1V Power Rail#hardware.voltage.volt'=1.09466666667V;;;0.99;1.21 '1.2V Power Rail#hardware.voltage.volt'=1.224V;;;1.08;1.32 '1.5V Power Rail#hardware.voltage.volt'=1.51066666667V;;;1.35;1.65 '1.8V Power Rail#hardware.voltage.volt'=1.812V;;;1.62;1.98 '2.5V Power Rail#hardware.voltage.volt'=2.504V;;;2.25;2.75 '3.3V Power Rail#hardware.voltage.volt'=3.332V;;;2.97;3.63 'hardware.voltage.count'=10;;;; + ... 9 --component=voltage --filter='voltage,0' OK: All 6 components are ok [6/6 voltages]. | '1.1V Power Rail#hardware.voltage.volt'=1.09466666667V;;;0.99;1.21 '1.2V Power Rail#hardware.voltage.volt'=1.224V;;;1.08;1.32 '1.5V Power Rail#hardware.voltage.volt'=1.51066666667V;;;1.35;1.65 '1.8V Power Rail#hardware.voltage.volt'=1.812V;;;1.62;1.98 '2.5V Power Rail#hardware.voltage.volt'=2.504V;;;2.25;2.75 '3.3V Power Rail#hardware.voltage.volt'=3.332V;;;2.97;3.63 'hardware.voltage.count'=6;;;; + ... 10 --component=temperature OK: All 5 components are ok [5/5 temperatures]. | 'Temperature near CPLD (inlet)#hardware.temperature.celsius'=40.9C;;;0.0;60.0 'Temperature near Cavium (outlet)#hardware.temperature.celsius'=51.6C;;;0.0;60.0 'Temperature near Management Port (inlet)#hardware.temperature.celsius'=38.0C;;;0.0;60.0 'Temperature near Switch (midboard)#hardware.temperature.celsius'=45.0C;;;0.0;60.0 'Temperature @ Cavium Core#hardware.temperature.celsius'=53.175C;;;0.0;85.0 'hardware.temperature.count'=5;;;; + ... 11 --component=temperature --filter='temperature,Management' OK: All 4 components are ok [4/4 temperatures]. | 'Temperature near CPLD (inlet)#hardware.temperature.celsius'=40.9C;;;0.0;60.0 'Temperature near Cavium (outlet)#hardware.temperature.celsius'=51.6C;;;0.0;60.0 'Temperature near Switch (midboard)#hardware.temperature.celsius'=45.0C;;;0.0;60.0 'Temperature @ Cavium Core#hardware.temperature.celsius'=53.175C;;;0.0;85.0 'hardware.temperature.count'=4;;;; + ... 12 --component=temperature --warning='temperature,.*,45' WARNING: Temperature 'Temperature near Cavium (outlet)' value is 51.6 C - Temperature 'Temperature @ Cavium Core' value is 53.175 C | 'Temperature near CPLD (inlet)#hardware.temperature.celsius'=40.9C;0:45;;0.0;60.0 'Temperature near Cavium (outlet)#hardware.temperature.celsius'=51.6C;0:45;;0.0;60.0 'Temperature near Management Port (inlet)#hardware.temperature.celsius'=38.0C;0:45;;0.0;60.0 'Temperature near Switch (midboard)#hardware.temperature.celsius'=45.0C;0:45;;0.0;60.0 'Temperature @ Cavium Core#hardware.temperature.celsius'=53.175C;0:45;;0.0;85.0 'hardware.temperature.count'=5;;;; + ... 13 --component=temperature --critical='temperature,.*,45' CRITICAL: Temperature 'Temperature near Cavium (outlet)' value is 51.6 C - Temperature 'Temperature @ Cavium Core' value is 53.175 C | 'Temperature near CPLD (inlet)#hardware.temperature.celsius'=40.9C;;0:45;0.0;60.0 'Temperature near Cavium (outlet)#hardware.temperature.celsius'=51.6C;;0:45;0.0;60.0 'Temperature near Management Port (inlet)#hardware.temperature.celsius'=38.0C;;0:45;0.0;60.0 'Temperature near Switch (midboard)#hardware.temperature.celsius'=45.0C;;0:45;0.0;60.0 'Temperature @ Cavium Core#hardware.temperature.celsius'=53.175C;;0:45;0.0;85.0 'hardware.temperature.count'=5;;;; + ... 14 --component=voltage --warning='voltage,.*,0' WARNING: Voltage '0.85V Power Rail' value is 0.860666666667 V - Voltage '3.3V SD Power Rail' value is 3.332 V - Voltage '0.9V Power Rail' value is 0.904 V - Voltage '1.0V Power Rail' value is 1.006 V - Voltage '1.1V Power Rail' value is 1.09466666667 V - Voltage '1.2V Power Rail' value is 1.224 V - Voltage '1.5V Power Rail' value is 1.51066666667 V - Voltage '1.8V Power Rail' value is 1.812 V - Voltage '2.5V Power Rail' value is 2.504 V - Voltage '3.3V Power Rail' value is 3.332 V | '0.85V Power Rail#hardware.voltage.volt'=0.860666666667V;0:0;;0.76;0.94 '3.3V SD Power Rail#hardware.voltage.volt'=3.332V;0:0;;2.97;3.63 '0.9V Power Rail#hardware.voltage.volt'=0.904V;0:0;;0.81;0.99 '1.0V Power Rail#hardware.voltage.volt'=1.006V;0:0;;0.9;1.1 '1.1V Power Rail#hardware.voltage.volt'=1.09466666667V;0:0;;0.99;1.21 '1.2V Power Rail#hardware.voltage.volt'=1.224V;0:0;;1.08;1.32 '1.5V Power Rail#hardware.voltage.volt'=1.51066666667V;0:0;;1.35;1.65 '1.8V Power Rail#hardware.voltage.volt'=1.812V;0:0;;1.62;1.98 '2.5V Power Rail#hardware.voltage.volt'=2.504V;0:0;;2.25;2.75 '3.3V Power Rail#hardware.voltage.volt'=3.332V;0:0;;2.97;3.63 'hardware.voltage.count'=10;;;; + ... 15 --component=voltage --critical='voltage,.*,0' CRITICAL: Voltage '0.85V Power Rail' value is 0.860666666667 V - Voltage '3.3V SD Power Rail' value is 3.332 V - Voltage '0.9V Power Rail' value is 0.904 V - Voltage '1.0V Power Rail' value is 1.006 V - Voltage '1.1V Power Rail' value is 1.09466666667 V - Voltage '1.2V Power Rail' value is 1.224 V - Voltage '1.5V Power Rail' value is 1.51066666667 V - Voltage '1.8V Power Rail' value is 1.812 V - Voltage '2.5V Power Rail' value is 2.504 V - Voltage '3.3V Power Rail' value is 3.332 V | '0.85V Power Rail#hardware.voltage.volt'=0.860666666667V;;0:0;0.76;0.94 '3.3V SD Power Rail#hardware.voltage.volt'=3.332V;;0:0;2.97;3.63 '0.9V Power Rail#hardware.voltage.volt'=0.904V;;0:0;0.81;0.99 '1.0V Power Rail#hardware.voltage.volt'=1.006V;;0:0;0.9;1.1 '1.1V Power Rail#hardware.voltage.volt'=1.09466666667V;;0:0;0.99;1.21 '1.2V Power Rail#hardware.voltage.volt'=1.224V;;0:0;1.08;1.32 '1.5V Power Rail#hardware.voltage.volt'=1.51066666667V;;0:0;1.35;1.65 '1.8V Power Rail#hardware.voltage.volt'=1.812V;;0:0;1.62;1.98 '2.5V Power Rail#hardware.voltage.volt'=2.504V;;0:0;2.25;2.75 '3.3V Power Rail#hardware.voltage.volt'=3.332V;;0:0;2.97;3.63 'hardware.voltage.count'=10;;;; diff --git a/tests/network/paloalto/api/fixed_date.pm b/tests/network/paloalto/api/fixed_date.pm new file mode 100644 index 0000000000..7726d3a692 --- /dev/null +++ b/tests/network/paloalto/api/fixed_date.pm @@ -0,0 +1,10 @@ +package fixed_date; + +# Copyright 2026-Present Centreon +# Always use the same fixed date to test certificate validity + +BEGIN { + *CORE::GLOBAL::time = sub { 1738368000 }; +} + +1 diff --git a/tests/network/paloalto/api/ha.robot b/tests/network/paloalto/api/ha.robot new file mode 100644 index 0000000000..a4425a927d --- /dev/null +++ b/tests/network/paloalto/api/ha.robot @@ -0,0 +1,44 @@ +*** Settings *** +Documentation Check PaloAlto High Availability (HA) status. + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=ha + +*** Test Cases *** +paloalto-ha ${tc} + [Tags] network paloalto api ha + ${command} Catenate + ... ${CMD} + ... --auth-type=api-key + ... --api-key=D@pAs$W@rD + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} OK: PA-850 status: local state: passive (priority: 110), peer state: active (priority: 100, conn: up), state sync: synchronized, HA1 link: up, HA2 link: up, HA mode: active-passive, build compatibility: Match + ... 2 --warning-peer-state='\\\%{peer_state_priority} =~ /active/' WARNING: PA-850 status: peer state: active (priority: 100, conn: up) + ... 3 --critical-peer-state='\\\%{peer_state_priority} =~ /active/' CRITICAL: PA-850 status: peer state: active (priority: 100, conn: up) + ... 4 --warning-state-sync='\\\%{state_sync} =~ /synchronized/' WARNING: PA-850 status: state sync: synchronized + ... 5 --critical-state-sync='\\\%{state_sync} =~ /synchronized/' CRITICAL: PA-850 status: state sync: synchronized + ... 6 --warning-ha1-link-status='\\\%{ha1_status} =~ /up/' WARNING: PA-850 status: HA1 link: up + ... 7 --critical-ha1-link-status='\\\%{ha1_status} =~ /up/' CRITICAL: PA-850 status: HA1 link: up + ... 8 --warning-ha2-link-status='\\\%{ha2_status} =~ /up/' WARNING: PA-850 status: HA2 link: up + ... 9 --critical-ha2-link-status='\\\%{ha2_status} =~ /up/' CRITICAL: PA-850 status: HA2 link: up + ... 10 --warning-build-compat='\\\%{build_compat} =~ /Match/' WARNING: PA-850 status: build compatibility: Match + ... 11 --critical-build-compat='\\\%{build_compat} =~ /Match/' CRITICAL: PA-850 status: build compatibility: Match diff --git a/tests/network/paloalto/api/licenses.robot b/tests/network/paloalto/api/licenses.robot new file mode 100644 index 0000000000..7e4c77331c --- /dev/null +++ b/tests/network/paloalto/api/licenses.robot @@ -0,0 +1,49 @@ +*** Settings *** +Documentation Check PaloAlto licenses status and expiration. + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${INJECT_PERL} -Mfixed_date -I${CURDIR} +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=licenses + +*** Test Cases *** +paloalto-licenses ${tc} + [Tags] network paloalto api licenses + + ${OLD_PERL5OPT}= Get Environment Variable PERL5OPT default= + Set Environment Variable PERL5OPT ${INJECT_PERL} ${OLD_PERL5OPT} + + ${command} Catenate + ... ${CMD} + ... --auth-type=api-key + ... --api-key=D@pAs$W@rD + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=13;;;0; 'Advanced DNS Security#license.expiration.days'=185d;;@0:0;-1; 'Advanced Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'Advanced URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'Advanced WildFire License#license.expiration.days'=185d;;@0:0;-1; 'DNS Security#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; 'Standard#license.expiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'WildFire License#license.expiration.days'=185d;;@0:0;-1; + ... 2 --include-license-name=Logging --warning-status='\\\%{expired}=~/no/' WARNING: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; + ... 3 --include-license-name=Logging --critical-status='\\\%{expired}=~/no/' CRITICAL: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; + ... 4 --include-license-name=GlobalProtect CRITICAL: license 'GlobalProtect Gateway' 0 days left | 'licenses.count'=2;;;0; 'GlobalProtect Gateway#license.expiration.days'=0d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; + ... 5 --warning-license-count=1000 OK: Licenses count: 11 - All licenses are ok + ... 6 --critical-license-count=1000 CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=9;;;0; 'DNS Security#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; 'Standard#license.expiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'WildFire License#license.expiration.days'=185d;;@0:0;-1; + ... 7 --include-license-name=SD OK: Licenses count: 1 - license 'SD WAN' expired: no, 185 days left | 'licenses.count'=1;;;0; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; + ... 8 --include-license-name=Logging --warning-expiration-days=1000 WARNING: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;0:1000;@0:0;-1; + ... 9 --include-license-name=Logging --critical-expiration-days=1000 CRITICAL: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;0:1000;-1; + ... 10 --include-license-name=Standard CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=1;;;0; 'Standard#license.expiration.days'=0d;;@0:0;-1; + ... 11 --include-license-name=Portal OK: Licenses count: 1 - license 'GlobalProtect Portal' expired: no, never expire | 'licenses.count'=1;;;0; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; diff --git a/tests/network/paloalto/api/mockoon-paloalto-api.json b/tests/network/paloalto/api/mockoon-paloalto-api.json new file mode 100644 index 0000000000..c854211e63 --- /dev/null +++ b/tests/network/paloalto/api/mockoon-paloalto-api.json @@ -0,0 +1,263 @@ +{ + "uuid": "919382d8-0f30-447f-abcd-45c98e84d7fe", + "lastMigration": 33, + "name": "PaloAlto API Mock Server", + "endpointPrefix": "", + "latency": 0, + "port": 3000, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + "type": "http", + "documentation": "Generate API Key", + "method": "post", + "endpoint": "/api", + "responses": [ + { + "uuid": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", + "body": "\n \n D@pAs$W@rD\n \n", + "latency": 0, + "statusCode": 200, + "label": "keygen success", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", + "type": "http", + "documentation": "Get HA Data", + "method": "get", + "endpoint": "/api", + "responses": [ + { + "uuid": "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b", + "body": "\n\n \n yes\n \n Active-Passive\n \n Mismatch\n 8868-8828\n 5.2.12\n 10.1.12\n dedicated-ha2\n 4875-5393\n no\n log-only\n 20240527.20193\n x.y.9.7/24\n configured\n PA-850\n Match\n x.y.7.10/24\n Match\n x.y.6.7/24\n f.g.h.i.j:10\n x.y.8.10/24\n Not Installed\n f.g.h.i.j:06\n 0\n 110\n 1\n passive\n 1\n 500\n Match\n Match\n Complete\n 500\n 1000\n 3000\n ethernet1/2\n 8000\n dedicated-ha1\n no\n x.y.56.7/24\n ethernet1/1\n 0\n Match\n 10000\n 0\n 8868-8828\n f.g.h.i.j:05\n 3889207\n 3\n \n auto\n 1\n \n \n ethernet\n yes\n 129-504\n Active-Passive\n Match\n Mismatch\n f.g.h.i.j:11\n Match\n \n \n 8868-8828\n 5.2.12\n 20240711.20198\n x.y.9.3\n 4875-5393\n PA-850\n x.y.7.6\n x.y.6.3\n x.y.8.6\n \n a.b.c.d.e:06\n 100\n active\n 1\n \n up\n heartbeat status\n \n User requested\n 10.1.12\n up\n a.b.c.d.e:10\n Not Installed\n x.y.56.6/24\n 1.0.3\n \n log-only\n yes\n up\n keep-alive status\n yes\n 0\n \n 8868-8828\n a.b.c.d.e:05\n \n up\n yes\n heartbeat status\n \n \n up\n yes\n keep-alive status\n \n 3889207\n \n a.b.c.d.e:11\n suspended\n yes\n 133-512\n Active-Passive\n \n up\n heartbeat status\n \n \n \n any\n yes\n \n \n \n \n up\n ethernet1/9\n \n \n up\n ethernet1/10\n \n \n all\n yes\n INSIDE Ports\n \n \n \n \n up\n ethernet1/11\n \n \n up\n ethernet1/12\n \n \n all\n yes\n OUTSIDE Ports\n \n \n \n \n \n any\n no\n \n \n \n synchronized\n yes\n \n \n", + "latency": 0, + "statusCode": 200, + "label": "HA", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "cmd", + "value": "", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "7808dac1-5a12-41ca-9fba-ded37545fc73", + "body": "\n\n \n \n \n \n 1\n False\n True\n Power Supply #1 (left)\n True\n \n \n 1\n False\n True\n Power Supply #2 (right)\n True\n \n \n \n \n \n \n 1\n Temperature near CPLD (inlet)\n 0.0\n 60.0\n False\n 40.9\n \n \n 1\n Temperature near Cavium (outlet)\n 0.0\n 60.0\n False\n 51.6\n \n \n 1\n Temperature near Management Port (inlet)\n 0.0\n 60.0\n False\n 38.0\n \n \n 1\n Temperature near Switch (midboard)\n 0.0\n 60.0\n False\n 45.0\n \n \n 1\n Temperature @ Cavium Core\n 0.0\n 85.0\n False\n 53.175\n \n \n \n \n \n \n 1\n False\n Fan #1 RPM\n 9157\n 2500\n \n \n 1\n False\n Fan #2 RPM\n 9557\n 2500\n \n \n 1\n False\n Fan #3 RPM\n 9418\n 2500\n \n \n \n \n \n \n 1\n 0.85V Power Rail\n 0.76\n 0.860666666667\n False\n 0.94\n \n \n 1\n 0.9V Power Rail\n 0.81\n 0.904\n False\n 0.99\n \n \n 1\n 1.0V Power Rail\n 0.9\n 1.006\n False\n 1.1\n \n \n 1\n 1.1V Power Rail\n 0.99\n 1.09466666667\n False\n 1.21\n \n \n 1\n 1.2V Power Rail\n 1.08\n 1.224\n False\n 1.32\n \n \n 1\n 1.5V Power Rail\n 1.35\n 1.51066666667\n False\n 1.65\n \n \n 1\n 1.8V Power Rail\n 1.62\n 1.812\n False\n 1.98\n \n \n 1\n 2.5V Power Rail\n 2.25\n 2.504\n False\n 2.75\n \n \n 1\n 3.3V Power Rail\n 2.97\n 3.332\n False\n 3.63\n \n \n 1\n 3.3V SD Power Rail\n 2.97\n 3.332\n False\n 3.63\n \n \n \n \n", + "latency": 0, + "statusCode": 200, + "label": "ENVIRONMENT", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "cmd", + "value": "", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "8909ebf2-6b23-52db-a0cb-3fde48656e84", + "body": "\n\n \n \n \n Advanced DNS Security\n Advanced DNS Security Subscription\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n Advanced Threat Prevention\n Advanced Threat Prevention\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n Advanced URL Filtering\n Palo Alto Networks Advanced URL License\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n Advanced WildFire License\n Access to Advanced WildFire signatures, logs, API\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n DNS Security\n Palo Alto Networks DNS Security License\n 123456789\n May 20, 2024\n August 05, 2025\n no\n I4876380\n \n \n GlobalProtect Gateway\n GlobalProtect Gateway License\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n GlobalProtect Portal\n GlobalProtect Portal License\n 123456789\n February 10, 2020\n Never\n no\n \n \n \n Logging Service\n Device Logging Service\n 123456789\n January 18, 2024\n April 26, 2028\n no\n \n <_Log_Storage_TB>1\n \n \n \n \n PAN-DB URL Filtering\n Palo Alto Networks URL Filtering License\n 123456789\n May 20, 2024\n August 05, 2025\n no\n I4287613\n \n \n SD WAN\n License to enable SD WAN feature\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n Standard\n 10 x 5 phone support; repair and replace hardware service\n 123456789\n February 25, 2021\n August 05, 2024\n yes\n \n \n \n Threat Prevention\n Threat Prevention\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n WildFire License\n WildFire signature feed, integrated WildFire logs, WildFire API\n 123456789\n May 20, 2024\n August 05, 2025\n no\n \n \n \n \n", + "latency": 0, + "statusCode": 200, + "label": "LICENSES", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "cmd", + "value": "", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "3dddb89e-01f4-4048-92d2-81992c5c17b7", + "body": "\n\n \n \n Firewall_Name-01\n x.y.z.7\n unknown\n 255.255.255.0\n x.y.z.254\n no\n unknown\n fe80::x:y:z:a/64\n a:b:c:d:e\n \n 98 days, 23:42:29\n Firewall_Name-01\n 800\n PA-850\n 123456789\n non-cloud\n 10.1.12\n 5.2.12\n 129-504\n 2024/05/26 15:42:11 CEST\n 8868-8828\n 2024/07/08 23:25:41 CEST\n 4875-5393\n 2024/07/10 13:03:40 CEST\n 8868-8828\n 2024/07/08 23:25:41 CEST\n 0\n unknown\n paloaltonetworks\n 889843-893745\n 2024/07/11 14:56:58 CEST\n Disabled\n 20240527.20193\n 1695498753\n 2023-09-23 19:52:33\n 98-260\n 2023/05/23 00:41:22 CEST\n 10.1.2\n \n \n dlp-1.0.3\n \n \n 800\n off\n off\n Disabled\n normal\n Valid\n \n \n", + "latency": 0, + "statusCode": 200, + "label": "SYSTEM", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "cmd", + "value": "", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "7f89e90c-80f8-4957-ac9a-26fe37fe81cd", + "body": "\n\n \n 60\n \n \n Unlimited\n 3600\n G256\n Addresse IP a.b.c.d\n Nom du tunnel 1\n ESP\n 2300285266\n 19\n 1430731122\n 1727\n 66\n \n Nom de la Gateway 1\n \n \n Unlimited\n 3600\n G256\n Addresse IP a.b.c.d\n Nom du tunnel 2\n ESP\n 2300285266\n 19\n 1430731122\n 1727\n 66\n \n Nom de la Gateway 2\n \n \n Unlimited\n 3600\n G256\n Addresse IP a.b.c.d\n Nom du tunnel 3\n ESP\n 2300285266\n 19\n 1430731122\n 1727\n 66\n \n Nom de la Gateway 3\n \n \n \n", + "latency": 0, + "statusCode": 200, + "label": "TUNNEL", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "cmd", + "value": "", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" + }, + { + "type": "route", + "uuid": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [], + "callbacks": [] +} \ No newline at end of file diff --git a/tests/network/paloalto/api/system.robot b/tests/network/paloalto/api/system.robot new file mode 100644 index 0000000000..6faa98cda7 --- /dev/null +++ b/tests/network/paloalto/api/system.robot @@ -0,0 +1,51 @@ +*** Settings *** +Documentation Check PaloAlto system information and status. + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${INJECT_PERL} -Mfixed_date -I${CURDIR} +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=system + +*** Test Cases *** +paloalto-system ${tc} + [Tags] network paloalto api system + + ${OLD_PERL5OPT}= Get Environment Variable PERL5OPT default= + Set Environment Variable PERL5OPT ${INJECT_PERL} ${OLD_PERL5OPT} + + ${command} Catenate + ... ${CMD} + ... --auth-type=api-key + ... --api-key=D@pAs$W@rD + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} OK: System uptime: 8552549 seconds, certificate status: Valid, operational mode: normal, software version: 10.1.12, WildFire mode: Disabled | 'system.uptime.seconds'=8552549s;;;0; + ... 2 --warning-uptime=:1000 WARNING: System uptime: 8552549 seconds | 'system.uptime.seconds'=8552549s;0:1000;;0; + ... 3 --critical-uptime=:1000 CRITICAL: System uptime: 8552549 seconds | 'system.uptime.seconds'=8552549s;;0:1000;0; + ... 4 --warning-certificate-status='\\\%{cert_status} =~ /Valid/' WARNING: System certificate status: Valid | 'system.uptime.seconds'=8552549s;;;0; + ... 5 --critical-certificate-status='\\\%{cert_status} =~ /Valid/' CRITICAL: System certificate status: Valid | 'system.uptime.seconds'=8552549s;;;0; + ... 6 --warning-software-version='\\\%{sw_version} !~ /2\.0/' WARNING: System software version: 10.1.12 | 'system.uptime.seconds'=8552549s;;;0; + ... 7 --critical-software-version='\\\%{sw_version} !~ /2\.0/' CRITICAL: System software version: 10.1.12 | 'system.uptime.seconds'=8552549s;;;0; + ... 8 --warning-operational-mode='\\\%{operational_mode} =~ /normal/' WARNING: System operational mode: normal | 'system.uptime.seconds'=8552549s;;;0; + ... 9 --critical-operational-mode='\\\%{operational_mode} =~ /normal/' CRITICAL: System operational mode: normal | 'system.uptime.seconds'=8552549s;;;0; + ... 10 --warning-wildfire-mode='\\\%{wildfire_mode} =~ /disabled/i' WARNING: System WildFire mode: Disabled | 'system.uptime.seconds'=8552549s;;;0; + ... 11 --critical-wildfire-mode='\\\%{wildfire_mode} =~ /disabled/i' CRITICAL: System WildFire mode: Disabled | 'system.uptime.seconds'=8552549s;;;0; + + diff --git a/tests/network/paloalto/api/tunnel.robot b/tests/network/paloalto/api/tunnel.robot new file mode 100644 index 0000000000..865d98719c --- /dev/null +++ b/tests/network/paloalto/api/tunnel.robot @@ -0,0 +1,43 @@ +*** Settings *** +Documentation Check PaloAlto IPsec VPN tunnels status and lifetime. + +Resource ${CURDIR}${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=network::paloalto::api::plugin +... --hostname=${HOSTNAME} +... --port=${APIPORT} +... --proto=http +... --mode=tunnel + +*** Test Cases *** +paloalto-tunnel ${tc} + [Tags] network paloalto api tunnel + + ${command} Catenate + ... ${CMD} + ... --auth-type=api-key + ... --api-key=D@pAs$W@rD + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} OK: Tunnels count: 3 - All tunnels are ok | 'tunnels.count'=3;;;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;;0; + ... 2 --include-gateway-name='ateway 3' OK: Tunnels count: 1 - tunnel 'Nom du tunnel 3' remain: 1727 seconds, encryption: G256, gateway: Nom de la Gateway 3 | 'tunnels.count'=1;;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;;0; + ... 3 --exclude-gateway-name='ateway 1' OK: Tunnels count: 2 - All tunnels are ok | 'tunnels.count'=2;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;;0; + ... 4 --include-tunnel-name='unnel 2' OK: Tunnels count: 1 - tunnel 'Nom du tunnel 2' remain: 1727 seconds, encryption: G256, gateway: Nom de la Gateway 2 | 'tunnels.count'=1;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; + ... 5 --exclude-tunnel-name='unnel 3' OK: Tunnels count: 2 - All tunnels are ok | 'tunnels.count'=2;;;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; + ... 6 --warning-tunnels-count=:1 WARNING: Tunnels count: 3 | 'tunnels.count'=3;0:1;;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;;0; + ... 7 --critical-tunnels-count=:1 CRITICAL: Tunnels count: 3 | 'tunnels.count'=3;;0:1;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;;0; + ... 8 --warning-remain-time=:1000 WARNING: tunnel 'Nom du tunnel 1' remain: 1727 seconds - tunnel 'Nom du tunnel 2' remain: 1727 seconds - tunnel 'Nom du tunnel 3' remain: 1727 seconds | 'tunnels.count'=3;;;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;0:1000;;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;0:1000;;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;0:1000;;0; + ... 9 --critical-remain-time=:1000 CRITICAL: tunnel 'Nom du tunnel 1' remain: 1727 seconds - tunnel 'Nom du tunnel 2' remain: 1727 seconds - tunnel 'Nom du tunnel 3' remain: 1727 seconds | 'tunnels.count'=3;;;0; 'Nom du tunnel 1#tunnel.remain.seconds'=1727s;;0:1000;0; 'Nom du tunnel 2#tunnel.remain.seconds'=1727s;;0:1000;0; 'Nom du tunnel 3#tunnel.remain.seconds'=1727s;;0:1000;0; diff --git a/tests/resources/spellcheck/stopwords.txt b/tests/resources/spellcheck/stopwords.txt index f9a47607e4..b806b076bd 100644 --- a/tests/resources/spellcheck/stopwords.txt +++ b/tests/resources/spellcheck/stopwords.txt @@ -18,6 +18,7 @@ Alertmanager allCapacity Alletra allsteps +Alto Ansible --api-filter-orgs api.ip-label.net @@ -266,6 +267,7 @@ out-mcast out-ucast overprovisioning packetloss +Palo partiallyActive passthrough --patch-redhat @@ -407,6 +409,7 @@ WaaS --warning-cpu-utilization --warning-na --warning-total-cpu-utilization +WildFire WLAN WLC WSMAN From 71bdbb0361063b1f00408813baac39ab40103815 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Wed, 25 Mar 2026 09:06:16 +0100 Subject: [PATCH 02/10] Update tests --- src/network/paloalto/api/custom/api.pm | 8 ++++---- tests/network/paloalto/api/licenses.robot | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/network/paloalto/api/custom/api.pm b/src/network/paloalto/api/custom/api.pm index a677d36ea4..0822d5d6d0 100644 --- a/src/network/paloalto/api/custom/api.pm +++ b/src/network/paloalto/api/custom/api.pm @@ -193,10 +193,10 @@ sub _http_request { return $self->{http}->request( url_path => '/api/', - get_param => [ - 'type=' . $options{type}, - 'cmd=' . $options{cmd} - ], + get_params => { + 'type' => $options{type}, + 'cmd' => $options{cmd} + }, header => [ $self->_build_auth_header(), 'Accept: application/xml' diff --git a/tests/network/paloalto/api/licenses.robot b/tests/network/paloalto/api/licenses.robot index 7e4c77331c..74154bb455 100644 --- a/tests/network/paloalto/api/licenses.robot +++ b/tests/network/paloalto/api/licenses.robot @@ -36,14 +36,14 @@ paloalto-licenses ${tc} Ctn Run Command And Check Result As Strings ${command} ${expected_result} Examples: tc extra_options expected_result -- - ... 1 ${EMPTY} CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=13;;;0; 'Advanced DNS Security#license.expiration.days'=185d;;@0:0;-1; 'Advanced Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'Advanced URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'Advanced WildFire License#license.expiration.days'=185d;;@0:0;-1; 'DNS Security#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; 'Standard#license.expiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'WildFire License#license.expiration.days'=185d;;@0:0;-1; - ... 2 --include-license-name=Logging --warning-status='\\\%{expired}=~/no/' WARNING: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; - ... 3 --include-license-name=Logging --critical-status='\\\%{expired}=~/no/' CRITICAL: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; - ... 4 --include-license-name=GlobalProtect CRITICAL: license 'GlobalProtect Gateway' 0 days left | 'licenses.count'=2;;;0; 'GlobalProtect Gateway#license.expiration.days'=0d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; - ... 5 --warning-license-count=1000 OK: Licenses count: 11 - All licenses are ok - ... 6 --critical-license-count=1000 CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=9;;;0; 'DNS Security#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.expiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; 'Logging Service#license.expiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.expiration.days'=185d;;@0:0;-1; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; 'Standard#license.expiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.expiration.days'=185d;;@0:0;-1; 'WildFire License#license.expiration.days'=185d;;@0:0;-1; - ... 7 --include-license-name=SD OK: Licenses count: 1 - license 'SD WAN' expired: no, 185 days left | 'licenses.count'=1;;;0; 'SD WAN#license.expiration.days'=185d;;@0:0;-1; - ... 8 --include-license-name=Logging --warning-expiration-days=1000 WARNING: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;0:1000;@0:0;-1; - ... 9 --include-license-name=Logging --critical-expiration-days=1000 CRITICAL: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.expiration.days'=1180d;;0:1000;-1; - ... 10 --include-license-name=Standard CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=1;;;0; 'Standard#license.expiration.days'=0d;;@0:0;-1; - ... 11 --include-license-name=Portal OK: Licenses count: 1 - license 'GlobalProtect Portal' expired: no, never expire | 'licenses.count'=1;;;0; 'GlobalProtect Portal#license.expiration.days'=-1d;;@0:0;-1; + ... 1 ${EMPTY} CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=13;;;0; 'Advanced DNS Security#license.empiration.days'=185d;;@0:0;-1; 'Advanced Threat Prevention#license.empiration.days'=185d;;@0:0;-1; 'Advanced URL Filtering#license.empiration.days'=185d;;@0:0;-1; 'Advanced WildFire License#license.empiration.days'=185d;;@0:0;-1; 'DNS Security#license.empiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.empiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.empiration.days'=-1d;;@0:0;-1; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.empiration.days'=185d;;@0:0;-1; 'SD WAN#license.empiration.days'=185d;;@0:0;-1; 'Standard#license.empiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.empiration.days'=185d;;@0:0;-1; 'WildFire License#license.empiration.days'=185d;;@0:0;-1; + ... 2 --include-license-name=Logging --warning-status='\\\%{expired}=~/no/' WARNING: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; + ... 3 --include-license-name=Logging --critical-status='\\\%{expired}=~/no/' CRITICAL: license 'Logging Service' expired: no | 'licenses.count'=1;;;0; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; + ... 4 --include-license-name=GlobalProtect OK: Licenses count: 2 - All licenses are ok | 'licenses.count'=2;;;0; 'GlobalProtect Gateway#license.empiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.empiration.days'=-1d;;@0:0;-1; + ... 5 --warning-licenses-count=1000: --include-license-name=Logging WARNING: Licenses count: 1 | 'licenses.count'=1;1000:;;0; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; + ... 6 --critical-licenses-count=1000: --include-license-name=Logging CRITICAL: Licenses count: 1 | 'licenses.count'=1;;1000:;0; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; + ... 7 --include-license-name=SD OK: Licenses count: 1 - license 'SD WAN' expired: no, 185 days left | 'licenses.count'=1;;;0; 'SD WAN#license.empiration.days'=185d;;@0:0;-1; + ... 8 --include-license-name=Logging --warning-expiration-days=1000 WARNING: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.empiration.days'=1180d;0:1000;@0:0;-1; + ... 9 --include-license-name=Logging --critical-expiration-days=1000 CRITICAL: license 'Logging Service' 1180 days left | 'licenses.count'=1;;;0; 'Logging Service#license.empiration.days'=1180d;;0:1000;-1; + ... 10 --include-license-name=Standard CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=1;;;0; 'Standard#license.empiration.days'=0d;;@0:0;-1; + ... 11 --include-license-name=Portal OK: Licenses count: 1 - license 'GlobalProtect Portal' expired: no, never expire | 'licenses.count'=1;;;0; 'GlobalProtect Portal#license.empiration.days'=-1d;;@0:0;-1; From 418d689027491f7832a983aa64697e96925d9df8 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Wed, 25 Mar 2026 13:54:55 +0100 Subject: [PATCH 03/10] Fix tests --- tests/network/paloalto/api/licenses.robot | 2 +- tests/network/paloalto/api/system.robot | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/network/paloalto/api/licenses.robot b/tests/network/paloalto/api/licenses.robot index 74154bb455..d4ea8bf163 100644 --- a/tests/network/paloalto/api/licenses.robot +++ b/tests/network/paloalto/api/licenses.robot @@ -33,7 +33,7 @@ paloalto-licenses ${tc} ... --api-key=D@pAs$W@rD ... ${extra_options} - Ctn Run Command And Check Result As Strings ${command} ${expected_result} + Ctn Run Command Without Connector And Check Result As Strings ${command} ${expected_result} Examples: tc extra_options expected_result -- ... 1 ${EMPTY} CRITICAL: license 'Standard' expired: yes, 0 days left | 'licenses.count'=13;;;0; 'Advanced DNS Security#license.empiration.days'=185d;;@0:0;-1; 'Advanced Threat Prevention#license.empiration.days'=185d;;@0:0;-1; 'Advanced URL Filtering#license.empiration.days'=185d;;@0:0;-1; 'Advanced WildFire License#license.empiration.days'=185d;;@0:0;-1; 'DNS Security#license.empiration.days'=185d;;@0:0;-1; 'GlobalProtect Gateway#license.empiration.days'=185d;;@0:0;-1; 'GlobalProtect Portal#license.empiration.days'=-1d;;@0:0;-1; 'Logging Service#license.empiration.days'=1180d;;@0:0;-1; 'PAN-DB URL Filtering#license.empiration.days'=185d;;@0:0;-1; 'SD WAN#license.empiration.days'=185d;;@0:0;-1; 'Standard#license.empiration.days'=0d;;@0:0;-1; 'Threat Prevention#license.empiration.days'=185d;;@0:0;-1; 'WildFire License#license.empiration.days'=185d;;@0:0;-1; diff --git a/tests/network/paloalto/api/system.robot b/tests/network/paloalto/api/system.robot index 6faa98cda7..79d9aef1c7 100644 --- a/tests/network/paloalto/api/system.robot +++ b/tests/network/paloalto/api/system.robot @@ -9,7 +9,6 @@ Test Timeout 120s *** Variables *** -${INJECT_PERL} -Mfixed_date -I${CURDIR} ${MOCKOON_JSON} ${CURDIR}${/}mockoon-paloalto-api.json ${HOSTNAME} 127.0.0.1 ${APIPORT} 3000 @@ -24,9 +23,6 @@ ${CMD} ${CENTREON_PLUGINS} paloalto-system ${tc} [Tags] network paloalto api system - ${OLD_PERL5OPT}= Get Environment Variable PERL5OPT default= - Set Environment Variable PERL5OPT ${INJECT_PERL} ${OLD_PERL5OPT} - ${command} Catenate ... ${CMD} ... --auth-type=api-key From cb3a5c553b1ef29e674f81efde9b0f5af89d2db1 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Wed, 1 Apr 2026 15:31:42 +0200 Subject: [PATCH 04/10] Rename tunnel => ipsec --- src/network/paloalto/api/mode/{tunnel.pm => ipsec.pm} | 2 +- src/network/paloalto/api/plugin.pm | 2 +- tests/network/paloalto/api/{tunnel.robot => ipsec.robot} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/network/paloalto/api/mode/{tunnel.pm => ipsec.pm} (99%) rename tests/network/paloalto/api/{tunnel.robot => ipsec.robot} (98%) diff --git a/src/network/paloalto/api/mode/tunnel.pm b/src/network/paloalto/api/mode/ipsec.pm similarity index 99% rename from src/network/paloalto/api/mode/tunnel.pm rename to src/network/paloalto/api/mode/ipsec.pm index 7f55e829de..f12d1d2f00 100644 --- a/src/network/paloalto/api/mode/tunnel.pm +++ b/src/network/paloalto/api/mode/ipsec.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package network::paloalto::api::mode::tunnel; +package network::paloalto::api::mode::ipsec; use base qw(centreon::plugins::templates::counter); diff --git a/src/network/paloalto/api/plugin.pm b/src/network/paloalto/api/plugin.pm index a22bcd35d8..99373650c5 100644 --- a/src/network/paloalto/api/plugin.pm +++ b/src/network/paloalto/api/plugin.pm @@ -35,7 +35,7 @@ sub new { 'ha' => 'network::paloalto::api::mode::ha', 'licenses' => 'network::paloalto::api::mode::licenses', 'system' => 'network::paloalto::api::mode::system', - 'tunnel' => 'network::paloalto::api::mode::tunnel' + 'ipsec' => 'network::paloalto::api::mode::ipsec' }; $self->{custom_modes}->{api} = 'network::paloalto::api::custom::api'; diff --git a/tests/network/paloalto/api/tunnel.robot b/tests/network/paloalto/api/ipsec.robot similarity index 98% rename from tests/network/paloalto/api/tunnel.robot rename to tests/network/paloalto/api/ipsec.robot index 865d98719c..a88592ee11 100644 --- a/tests/network/paloalto/api/tunnel.robot +++ b/tests/network/paloalto/api/ipsec.robot @@ -17,10 +17,10 @@ ${CMD} ${CENTREON_PLUGINS} ... --hostname=${HOSTNAME} ... --port=${APIPORT} ... --proto=http -... --mode=tunnel +... --mode=ipsec *** Test Cases *** -paloalto-tunnel ${tc} +paloalto-ipsec ${tc} [Tags] network paloalto api tunnel ${command} Catenate From 33107f977d12cf28f45c06557d005de62a85f4cb Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Thu, 2 Apr 2026 11:26:51 +0200 Subject: [PATCH 05/10] Update plugin --- src/network/paloalto/api/mode/ha.pm | 129 +++++++++++++--------- src/network/paloalto/api/mode/licenses.pm | 4 +- tests/network/paloalto/api/ha.robot | 6 +- 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src/network/paloalto/api/mode/ha.pm b/src/network/paloalto/api/mode/ha.pm index f691b00a7d..bcddd9495f 100644 --- a/src/network/paloalto/api/mode/ha.pm +++ b/src/network/paloalto/api/mode/ha.pm @@ -24,12 +24,13 @@ use base qw(centreon::plugins::templates::counter); use strict; use warnings; +use Digest::SHA qw(sha256_hex); use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); -use centreon::plugins::constants qw(:counters); +use centreon::plugins::constants qw(:counters :values); sub new { my ($class, %options) = @_; - my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1, force_new_perfdata => 1); bless $self, $class; return $self; @@ -39,27 +40,29 @@ sub set_counters { my ($self, %options) = @_; $self->{maps_counters_type} = [ - { name => 'ha', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_ha_output' } + { name => 'ha', type => COUNTER_TYPE_GLOBAL, cb_prefix_output => 'prefix_ha_output', skipped_code => { BUFFER_CREATION() => 1 } } ]; $self->{maps_counters}->{ha} = [ { label => 'local-state', type => COUNTER_KIND_TEXT, + critical_default => '%{local_state} !~ /^(?:active|passive)$/i', set => { - key_values => [ { name => 'local_state_priority' } ], - output_template => '%s', + key_values => [ { name => 'local_state' }, { name => 'local_priority' } ], + closure_custom_calc => $self->can('custom_local_state_calc'), + closure_custom_output => $self->can('output_local_state'), closure_custom_threshold_check => \&catalog_status_threshold_ng } }, { label => 'peer-state', type => COUNTER_KIND_TEXT, - warning_default => '%{peer_state_priority} =~ /suspicious/i', - critical_default => '%{peer_state_priority} !~ /active|passive/i || %{peer_state_priority} !~ /up/i', + critical_default => '%{peer_state} !~ /^(?:active|passive)$/i || %{peer_conn_status} ne "up"', set => { - key_values => [ { name => 'peer_state_priority' } ], - output_template => '%s', + key_values => [ { name => 'peer_state' }, { name => 'peer_priority' }, { name => 'peer_conn_status' } ], + closure_custom_calc => $self->can('custom_peer_state_calc'), + closure_custom_output => $self->can('output_peer_state'), closure_custom_threshold_check => \&catalog_status_threshold_ng } }, @@ -121,6 +124,56 @@ sub prefix_ha_output { return "$model status: "; } +sub custom_local_state_calc { + my ($self, %options) = @_; + + # define a "local_state_last" variable for compatibility with paloalto::ssh plugin + $self->{result_values}->{local_state_last} = $options{old_datas}->{$self->{instance} . '_local_state'}; + $self->{result_values}->{local_state} = $options{new_datas}->{$self->{instance} . '_local_state'}; + $self->{result_values}->{local_priority} = $options{new_datas}->{$self->{instance} . '_local_priority'}; + if (!defined($options{old_datas}->{$self->{instance} . '_local_state'})) { + $self->{error_msg} = "buffer creation"; + return BUFFER_CREATION; + } + + return 0; +} + +sub custom_peer_state_calc { + my ($self, %options) = @_; + + # define a "peer_state_last" variable for compatibility with paloalto::ssh plugin + $self->{result_values}->{peer_state_last} = $options{old_datas}->{$self->{instance} . '_peer_state'}; + $self->{result_values}->{peer_state} = $options{new_datas}->{$self->{instance} . '_peer_state'}; + $self->{result_values}->{peer_priority} = $options{new_datas}->{$self->{instance} . '_peer_priority'}; + $self->{result_values}->{peer_conn_status} = $options{new_datas}->{$self->{instance} . '_peer_conn_status'}; + if (!defined($options{old_datas}->{$self->{instance} . '_peer_state'})) { + $self->{error_msg} = "buffer creation"; + return BUFFER_CREATION; + } + + return 0; +} + +sub output_local_state { + my ($self, %options) = @_; + return sprintf('local state: %s (priority: %s)%s', + $self->{result_values}->{local_state}, + $self->{result_values}->{local_priority}, + $self->{output}->is_verbose() ? " previous local state: ".$self->{result_values}->{local_state_last} : "" + ); +} + +sub output_peer_state { + my ($self, %options) = @_; + return sprintf('peer state: %s (priority: %s, conn: %s)%s', + $self->{result_values}->{peer_state}, + $self->{result_values}->{peer_priority}, + $self->{result_values}->{peer_conn_status}, + $self->{output}->is_verbose() ? " previous peer state: ".$self->{result_values}->{peer_state_last} : "" + ); +} + sub manage_selection { my ($self, %options) = @_; @@ -140,14 +193,20 @@ sub manage_selection { $self->{ha} = { platform_model => $platform_model, - local_state_priority => "local state: $local_state (priority: " . $local->{priority} . ")", - peer_state_priority => "peer state: $peer_state (priority: " . $peer->{priority} . ", conn: " . lc($peer->{'conn-status'}) . ")", - state_sync => lc($group->{'running-sync'}), - ha1_status => lc($peer->{'conn-ha1'}->{'conn-status'}), - ha2_status => lc($peer->{'conn-ha2'}->{'conn-status'}), + local_state => $local_state, + local_priority => $local->{priority} // '', + peer_state => $peer_state, + peer_priority => $peer->{priority} // '', + peer_conn_status => lc($peer->{'conn-status'} // ''), + state_sync => lc($group->{'running-sync'} // ''), + ha1_status => lc($peer->{'conn-ha1'}->{'conn-status'} // ''), + ha2_status => lc($peer->{'conn-ha2'}->{'conn-status'} // ''), ha_mode => $ha_mode, - build_compat => $peer->{'build-compat'} || $local->{'build-compat'} + build_compat => $local->{'build-compat'} // '' }; + + $self->{cache_name} = "paloalto_api_" . $self->{mode} . '_' . $options{custom}->get_hostname() . '_' . + sha256_hex($self->{option_results}->{filter_counters} // 'all'); } 1; @@ -160,40 +219,25 @@ Check Palo Alto High Availability (HA) status. =over 8 -=item B<--unknown-local-state> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{local_state}, %{local_priority} - =item B<--warning-local-state> Define the conditions to match for the status to be WARNING. -You can use the following variables: %{local_state}, %{local_priority} +You can use the following variables: %{local_state}, %{local_priority}, %{local_state_last} =item B<--critical-local-state> Define the conditions to match for the status to be CRITICAL (default: '%{local_state} !~ /^(?:active|passive)$/i'). -You can use the following variables: %{local_state}, %{local_priority} - -=item B<--unknown-peer-state> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} +You can use the following variables: %{local_state}, %{local_priority}, %{local_state_last} =item B<--warning-peer-state> Define the conditions to match for the status to be WARNING. -You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} +You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status}, %{peer_state_last} =item B<--critical-peer-state> -Define the conditions to match for the status to be CRITICAL (default: '%{peer_state} !~ /^(?:active|passive|unknown)$/i || %{peer_conn_status} ne "up"'). -You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status} - -=item B<--unknown-state-sync> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{state_sync} +Define the conditions to match for the status to be CRITICAL (default: '%{peer_state} !~ /^(?:active|passive)$/i || %{peer_conn_status} ne "up"'). +You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status}, %{peer_state_last} =item B<--warning-state-sync> @@ -205,11 +249,6 @@ You can use the following variables: %{state_sync} Define the conditions to match for the status to be CRITICAL (default: '%{state_sync} !~ /^synchronized|complete$/i'). You can use the following variables: %{state_sync} -=item B<--unknown-ha1-link-status> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{ha1_status} - =item B<--warning-ha1-link-status> Define the conditions to match for the status to be WARNING. @@ -220,11 +259,6 @@ You can use the following variables: %{ha1_status} Define the conditions to match for the status to be CRITICAL (default: '%{ha1_status} ne "up"'). You can use the following variables: %{ha1_status} -=item B<--unknown-ha2-link-status> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{ha2_status} - =item B<--warning-ha2-link-status> Define the conditions to match for the status to be WARNING. @@ -235,11 +269,6 @@ You can use the following variables: %{ha2_status} Define the conditions to match for the status to be CRITICAL (default: '%{ha2_status} ne "up"'). You can use the following variables: %{ha2_status} -=item B<--unknown-build-compat> - -Define the conditions to match for the status to be UNKNOWN. -You can use the following variables: %{build_compat} - =item B<--warning-build-compat> Define the conditions to match for the status to be WARNING (default: '%{build_compat} ne "Match"'). diff --git a/src/network/paloalto/api/mode/licenses.pm b/src/network/paloalto/api/mode/licenses.pm index a714d1bfa8..d21badde5a 100644 --- a/src/network/paloalto/api/mode/licenses.pm +++ b/src/network/paloalto/api/mode/licenses.pm @@ -86,7 +86,7 @@ sub set_counters { critical_default => '@0', set => { key_values => [ { name => 'days_left' }, { name => 'feature' } ], - closure_custom_output => \&custom_expiration_output, + closure_custom_output => $self->can('custom_expiration_output'), perfdatas => [ { template => '%s', unit => 'd', min => -1, label_extra_instance => 1, instance_use => 'feature' } @@ -140,7 +140,7 @@ sub manage_selection { if ($exp_date) { my $now = DateTime->now(time_zone => 'UTC'); $days_left = int(($exp_date->epoch() - $now->epoch()) / 86400); - $days_left = 0 if $days_left < 0; + $days_left = 0 if $days_left < 0; } } $self->{licenses}->{$feature} = { diff --git a/tests/network/paloalto/api/ha.robot b/tests/network/paloalto/api/ha.robot index a4425a927d..5140b34ae1 100644 --- a/tests/network/paloalto/api/ha.robot +++ b/tests/network/paloalto/api/ha.robot @@ -31,9 +31,9 @@ paloalto-ha ${tc} Ctn Run Command And Check Result As Strings ${command} ${expected_result} Examples: tc extra_options expected_result -- - ... 1 ${EMPTY} OK: PA-850 status: local state: passive (priority: 110), peer state: active (priority: 100, conn: up), state sync: synchronized, HA1 link: up, HA2 link: up, HA mode: active-passive, build compatibility: Match - ... 2 --warning-peer-state='\\\%{peer_state_priority} =~ /active/' WARNING: PA-850 status: peer state: active (priority: 100, conn: up) - ... 3 --critical-peer-state='\\\%{peer_state_priority} =~ /active/' CRITICAL: PA-850 status: peer state: active (priority: 100, conn: up) + ... 1 ${EMPTY} OK: PA-850 status: state sync: synchronized, HA1 link: up, HA2 link: up, HA mode: active-passive, build compatibility: Match + ... 2 --warning-peer-state='\\\%{peer_state} =~ /active/' WARNING: PA-850 status: peer state: active (priority: 100, conn: up) + ... 3 --critical-peer-state='\\\%{peer_state} =~ /active/' CRITICAL: PA-850 status: peer state: active (priority: 100, conn: up) ... 4 --warning-state-sync='\\\%{state_sync} =~ /synchronized/' WARNING: PA-850 status: state sync: synchronized ... 5 --critical-state-sync='\\\%{state_sync} =~ /synchronized/' CRITICAL: PA-850 status: state sync: synchronized ... 6 --warning-ha1-link-status='\\\%{ha1_status} =~ /up/' WARNING: PA-850 status: HA1 link: up From 640af89c510b8f09e20edb781cfcba15dff7aa15 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Thu, 2 Apr 2026 11:37:56 +0200 Subject: [PATCH 06/10] Update plugin --- src/network/paloalto/api/mode/ha.pm | 12 ++++++------ src/network/paloalto/api/mode/licenses.pm | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/network/paloalto/api/mode/ha.pm b/src/network/paloalto/api/mode/ha.pm index bcddd9495f..c4182b7254 100644 --- a/src/network/paloalto/api/mode/ha.pm +++ b/src/network/paloalto/api/mode/ha.pm @@ -47,7 +47,7 @@ sub set_counters { { label => 'local-state', type => COUNTER_KIND_TEXT, - critical_default => '%{local_state} !~ /^(?:active|passive)$/i', + critical_default => '%{local_state} !~ /^(?:active|passive)$/', set => { key_values => [ { name => 'local_state' }, { name => 'local_priority' } ], closure_custom_calc => $self->can('custom_local_state_calc'), @@ -58,7 +58,7 @@ sub set_counters { { label => 'peer-state', type => COUNTER_KIND_TEXT, - critical_default => '%{peer_state} !~ /^(?:active|passive)$/i || %{peer_conn_status} ne "up"', + critical_default => '%{peer_state} !~ /^(?:active|passive)$/ || %{peer_conn_status} ne "up"', set => { key_values => [ { name => 'peer_state' }, { name => 'peer_priority' }, { name => 'peer_conn_status' } ], closure_custom_calc => $self->can('custom_peer_state_calc'), @@ -69,7 +69,7 @@ sub set_counters { { label => 'state-sync', type => COUNTER_KIND_TEXT, - critical_default => '%{state_sync} !~ /^synchronized|complete$/i', + critical_default => '%{state_sync} !~ /^synchronized|complete$/', set => { key_values => [ { name => 'state_sync' } ], output_template => 'state sync: %s', @@ -226,7 +226,7 @@ You can use the following variables: %{local_state}, %{local_priority}, %{local_ =item B<--critical-local-state> -Define the conditions to match for the status to be CRITICAL (default: '%{local_state} !~ /^(?:active|passive)$/i'). +Define the conditions to match for the status to be CRITICAL (default: '%{local_state} !~ /^(?:active|passive)$/'). You can use the following variables: %{local_state}, %{local_priority}, %{local_state_last} =item B<--warning-peer-state> @@ -236,7 +236,7 @@ You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_con =item B<--critical-peer-state> -Define the conditions to match for the status to be CRITICAL (default: '%{peer_state} !~ /^(?:active|passive)$/i || %{peer_conn_status} ne "up"'). +Define the conditions to match for the status to be CRITICAL (default: '%{peer_state} !~ /^(?:active|passive)$/ || %{peer_conn_status} ne "up"'). You can use the following variables: %{peer_state}, %{peer_priority}, %{peer_conn_status}, %{peer_state_last} =item B<--warning-state-sync> @@ -246,7 +246,7 @@ You can use the following variables: %{state_sync} =item B<--critical-state-sync> -Define the conditions to match for the status to be CRITICAL (default: '%{state_sync} !~ /^synchronized|complete$/i'). +Define the conditions to match for the status to be CRITICAL (default: '%{state_sync} !~ /^synchronized|complete$/'). You can use the following variables: %{state_sync} =item B<--warning-ha1-link-status> diff --git a/src/network/paloalto/api/mode/licenses.pm b/src/network/paloalto/api/mode/licenses.pm index d21badde5a..d9c4f46902 100644 --- a/src/network/paloalto/api/mode/licenses.pm +++ b/src/network/paloalto/api/mode/licenses.pm @@ -73,7 +73,7 @@ sub set_counters { { label => 'status', type => COUNTER_KIND_TEXT, - critical_default => '%{expired} =~ /yes/i', + critical_default => '%{expired} =~ /yes/', set => { key_values => [ { name => 'expired' }, { name => 'feature' } ], output_template => 'expired: %s', @@ -182,7 +182,7 @@ You can use the following variables: %{expired} =item B<--critical-status> -Define the conditions to match for the status to be CRITICAL (default: '%{expired} =~ /yes/i'). +Define the conditions to match for the status to be CRITICAL (default: '%{expired} =~ /yes/'). You can use the following variables: %{expired} =item B<--warning-expiration-days> From 8e18ee99a34cc6a21ba0bf3fa9020671857a31a6 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Wed, 8 Apr 2026 09:11:58 +0200 Subject: [PATCH 07/10] Rename package --- packaging/centreon-plugin-Network-Paloalto-Api/pkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json b/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json index fa42427249..4fa99b2092 100644 --- a/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json +++ b/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json @@ -1,5 +1,5 @@ { - "pkg_name": "centreon-plugin-Network-Paloalto-Api", + "pkg_name": "centreon-plugin-Network-Firewalls-Paloalto-Standard-Api", "pkg_summary": "Centreon Plugin to monitor Palo Alto Networks firewalls using API XML", "plugin_name": "centreon_paloalto_api.pl", "files": [ From 58f39fec1ac4af62e522881e631caa5cd77d38b4 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Wed, 8 Apr 2026 16:52:48 +0200 Subject: [PATCH 08/10] Update --- .../deb.json | 0 .../pkg.json | 0 .../rpm.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packaging/{centreon-plugin-Network-Paloalto-Api => centreon-plugin-Network-Firewalls-Paloalto-Standard-Api}/deb.json (100%) rename packaging/{centreon-plugin-Network-Paloalto-Api => centreon-plugin-Network-Firewalls-Paloalto-Standard-Api}/pkg.json (100%) rename packaging/{centreon-plugin-Network-Paloalto-Api => centreon-plugin-Network-Firewalls-Paloalto-Standard-Api}/rpm.json (100%) diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/deb.json b/packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/deb.json similarity index 100% rename from packaging/centreon-plugin-Network-Paloalto-Api/deb.json rename to packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/deb.json diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/pkg.json b/packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/pkg.json similarity index 100% rename from packaging/centreon-plugin-Network-Paloalto-Api/pkg.json rename to packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/pkg.json diff --git a/packaging/centreon-plugin-Network-Paloalto-Api/rpm.json b/packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/rpm.json similarity index 100% rename from packaging/centreon-plugin-Network-Paloalto-Api/rpm.json rename to packaging/centreon-plugin-Network-Firewalls-Paloalto-Standard-Api/rpm.json From 4732d06c549ac7ec291b10c93f785e345609162a Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Thu, 9 Apr 2026 14:50:13 +0200 Subject: [PATCH 09/10] Update after review --- src/network/paloalto/api/mode/components/fan.pm | 6 +++--- src/network/paloalto/api/mode/components/psu.pm | 4 ++-- src/network/paloalto/api/mode/components/temperature.pm | 6 +++--- src/network/paloalto/api/mode/components/voltage.pm | 6 +++--- src/network/paloalto/api/mode/environment.pm | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/network/paloalto/api/mode/components/fan.pm b/src/network/paloalto/api/mode/components/fan.pm index f92e65f0fe..a93726d6ce 100644 --- a/src/network/paloalto/api/mode/components/fan.pm +++ b/src/network/paloalto/api/mode/components/fan.pm @@ -42,9 +42,9 @@ sub check { $result->{description}, $result->{alarm}, $instance, $result->{rpm})); - my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + my $alarm_status = $self->get_severity(label => 'default', section => 'fan', value => $result->{alarm}); $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Fan '%s' alarm is %s", $result->{description}, $alarm_status)) - unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); unless (is_empty($result->{rpm})){ my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( @@ -53,7 +53,7 @@ sub check { value => $result->{rpm} ); $self->{output}->output_add(severity => $exit, short_msg => sprintf("Fan '%s' rpm is %s", $result->{description}, $result->{rpm})) - unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); my $label = $result->{description} . '#hardware.fan.speed.rpm'; $self->{output}->perfdata_add( diff --git a/src/network/paloalto/api/mode/components/psu.pm b/src/network/paloalto/api/mode/components/psu.pm index 21aeab0f12..0e3a37216d 100644 --- a/src/network/paloalto/api/mode/components/psu.pm +++ b/src/network/paloalto/api/mode/components/psu.pm @@ -42,9 +42,9 @@ sub check { $result->{description}, $result->{alarm}, $instance, $result->{inserted})); - my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + my $alarm_status = $self->get_severity(label => 'default', section => 'psu', value => $result->{alarm}); $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Power supply '%s' alarm is %s", $result->{description}, $alarm_status)) - unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); } } diff --git a/src/network/paloalto/api/mode/components/temperature.pm b/src/network/paloalto/api/mode/components/temperature.pm index 1a1f8af614..a2a49b679a 100644 --- a/src/network/paloalto/api/mode/components/temperature.pm +++ b/src/network/paloalto/api/mode/components/temperature.pm @@ -42,9 +42,9 @@ sub check { $result->{description}, $result->{alarm}, $instance, $result->{value})); - my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + my $alarm_status = $self->get_severity(label => 'default', section => 'temperature', value => $result->{alarm}); $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Temperature '%s' alarm is %s", $result->{description}, $alarm_status)) - unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); unless (is_empty($result->{value})) { my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( @@ -53,7 +53,7 @@ sub check { value => $result->{value} ); $self->{output}->output_add(severity => $exit, short_msg => sprintf("Temperature '%s' value is %s C", $result->{description}, $result->{value})) - unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); my $label = $result->{description} . '#hardware.temperature.celsius'; $self->{output}->perfdata_add( diff --git a/src/network/paloalto/api/mode/components/voltage.pm b/src/network/paloalto/api/mode/components/voltage.pm index 9347ba0208..cd372c916e 100644 --- a/src/network/paloalto/api/mode/components/voltage.pm +++ b/src/network/paloalto/api/mode/components/voltage.pm @@ -42,9 +42,9 @@ sub check { $result->{description}, $result->{alarm}, $instance, $result->{value})); - my $alarm_status = $result->{alarm} =~ /true/i ? 'CRITICAL' : 'OK'; + my $alarm_status = $self->get_severity(label => 'default', section => 'voltage', value => $result->{alarm}); $self->{output}->output_add(severity => $alarm_status, short_msg => sprintf("Voltage '%s' alarm is %s", $result->{description}, $alarm_status)) - unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $alarm_status, compare => 'ok', litteral => 1); unless (is_empty($result->{value})) { my ($exit, $warn, $crit, $checked) = $self->get_severity_numeric( @@ -53,7 +53,7 @@ sub check { value => $result->{value} ); $self->{output}->output_add(severity => $exit, short_msg => sprintf("Voltage '%s' value is %s V", $result->{description}, $result->{value})) - unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); + unless $self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1); my $label = $result->{description} . '#hardware.voltage.volt'; $self->{output}->perfdata_add( diff --git a/src/network/paloalto/api/mode/environment.pm b/src/network/paloalto/api/mode/environment.pm index 38f05593d4..6d8e5501a0 100644 --- a/src/network/paloalto/api/mode/environment.pm +++ b/src/network/paloalto/api/mode/environment.pm @@ -71,7 +71,7 @@ sub api_execute { description => $entry->{description} // "Temperature $temp_idx", value => $entry->{DegreesC} // 0, min => $entry->{min} // 0, - max => $entry->{max} // 100, + max => $entry->{max} // 0, alarm => $entry->{alarm} // 'False' }; } @@ -108,7 +108,7 @@ sub api_execute { description => $entry->{description} // "Voltage $voltage_idx", value => $entry->{Volts} // 0, min => $entry->{min} // 0, - max => $entry->{max} // 5, + max => $entry->{max} // 0, alarm => $entry->{alarm} // 'False' }; } From 186da12bb854424e6574eb2d8a6fa3e53d41ce86 Mon Sep 17 00:00:00 2001 From: Sylvain Cresto Date: Thu, 9 Apr 2026 17:32:14 +0200 Subject: [PATCH 10/10] Update --- src/network/paloalto/api/mode/environment.pm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/network/paloalto/api/mode/environment.pm b/src/network/paloalto/api/mode/environment.pm index 6d8e5501a0..68e6538d7c 100644 --- a/src/network/paloalto/api/mode/environment.pm +++ b/src/network/paloalto/api/mode/environment.pm @@ -69,9 +69,9 @@ sub api_execute { my $instance = "thermal_slot" . ($entry->{slot} // 1) . "_index" . $temp_idx; $self->{data}->{temperatures}->{$instance} = { description => $entry->{description} // "Temperature $temp_idx", - value => $entry->{DegreesC} // 0, - min => $entry->{min} // 0, - max => $entry->{max} // 0, + value => $entry->{DegreesC} // '', + min => $entry->{min} // '', + max => $entry->{max} // '', alarm => $entry->{alarm} // 'False' }; } @@ -88,8 +88,8 @@ sub api_execute { my $instance = "fan_slot" . ($entry->{slot} // 1) . "_index" . $fan_idx; $self->{data}->{fans}->{$instance} = { description => $entry->{description} // "Fan $fan_idx", - rpm => $entry->{RPMs} // 0, - min => $entry->{min} // 0, + rpm => $entry->{RPMs} // '', + min => $entry->{min} // '', alarm => $entry->{alarm} // 'False' }; } @@ -106,9 +106,9 @@ sub api_execute { my $instance = "voltage_slot" . ($entry->{slot} // 1) . "_index" . $voltage_idx; $self->{data}->{voltages}->{$instance} = { description => $entry->{description} // "Voltage $voltage_idx", - value => $entry->{Volts} // 0, - min => $entry->{min} // 0, - max => $entry->{max} // 0, + value => $entry->{Volts} // '', + min => $entry->{min} // '', + max => $entry->{max} // '', alarm => $entry->{alarm} // 'False' }; }