From fa3579e61336714ef5188f94c3dd66cf4a46db69 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 10 Dec 2018 18:48:18 +0100 Subject: [PATCH] add proxmox ve plugin (#1269) * First Release of Proxmox VE Plugins Discovery for Nodes, VMs (LXC & QEMU) and Storage Node Usage, VM Usage and Storage Usage. * Add Doc Specification about Realm and Privileges * Added API Ticket Cache Management * changes after code review --- apps/proxmox/ve/restapi/custom/api.pm | 558 +++++++++++++++++++ apps/proxmox/ve/restapi/mode/listnodes.pm | 99 ++++ apps/proxmox/ve/restapi/mode/liststorages.pm | 101 ++++ apps/proxmox/ve/restapi/mode/listvms.pm | 104 ++++ apps/proxmox/ve/restapi/mode/nodeusage.pm | 426 ++++++++++++++ apps/proxmox/ve/restapi/mode/storageusage.pm | 285 ++++++++++ apps/proxmox/ve/restapi/mode/version.pm | 64 +++ apps/proxmox/ve/restapi/mode/vmusage.pm | 369 ++++++++++++ apps/proxmox/ve/restapi/plugin.pm | 71 +++ 9 files changed, 2077 insertions(+) create mode 100644 apps/proxmox/ve/restapi/custom/api.pm create mode 100644 apps/proxmox/ve/restapi/mode/listnodes.pm create mode 100644 apps/proxmox/ve/restapi/mode/liststorages.pm create mode 100644 apps/proxmox/ve/restapi/mode/listvms.pm create mode 100644 apps/proxmox/ve/restapi/mode/nodeusage.pm create mode 100644 apps/proxmox/ve/restapi/mode/storageusage.pm create mode 100644 apps/proxmox/ve/restapi/mode/version.pm create mode 100644 apps/proxmox/ve/restapi/mode/vmusage.pm create mode 100644 apps/proxmox/ve/restapi/plugin.pm diff --git a/apps/proxmox/ve/restapi/custom/api.pm b/apps/proxmox/ve/restapi/custom/api.pm new file mode 100644 index 000000000..2ab741aab --- /dev/null +++ b/apps/proxmox/ve/restapi/custom/api.pm @@ -0,0 +1,558 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::custom::api; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; +use centreon::plugins::http; +use centreon::plugins::statefile; +use JSON:XS; +use Digest::MD5 qw(md5_hex); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + if (!defined($options{output})) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3; + } + if (!defined($options{options})) { + $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); + $options{output}->option_exit(); + } + + if (!defined($options{noptions})) { + $options{options}->add_options(arguments => + { + "hostname:s" => { name => 'hostname' }, + "port:s" => { name => 'port'}, + "proto:s" => { name => 'proto' }, + "api-username:s" => { name => 'api_username' }, + "api-password:s" => { name => 'api_password' }, + "realm:s" => { name => 'realm' }, + "proxyurl:s" => { name => 'proxyurl' }, + "timeout:s" => { name => 'timeout' }, + "ssl-opt:s@" => { name => 'ssl_opt' }, + "timeout:s" => { name => 'timeout', default => 30 }, + "reload-cache-time:s" => { name => 'reload_cache_time', default => 7200 }, + }); + } + + $options{options}->add_help(package => __PACKAGE__, sections => 'REST API OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{mode} = $options{mode}; + $self->{http} = centreon::plugins::http->new(output => $self->{output}); + $self->{cache} = centreon::plugins::statefile->new(%options); + + return $self; +} + +sub set_options { + my ($self, %options) = @_; + + $self->{option_results} = $options{option_results}; +} + +sub set_defaults { + my ($self, %options) = @_; + + foreach (keys %{$options{default}}) { + if ($_ eq $self->{mode}) { + for (my $i = 0; $i < scalar(@{$options{default}->{$_}}); $i++) { + foreach my $opt (keys %{$options{default}->{$_}[$i]}) { + if (!defined($self->{option_results}->{$opt}[$i])) { + $self->{option_results}->{$opt}[$i] = $options{default}->{$_}[$i]->{$opt}; + } + } + } + } + } +} + +sub check_options { + my ($self, %options) = @_; + + $self->{hostname} = (defined($self->{option_results}->{hostname})) ? $self->{option_results}->{hostname} : undef; + $self->{port} = (defined($self->{option_results}->{port})) ? $self->{option_results}->{port} : 8006; + $self->{proto} = (defined($self->{option_results}->{proto})) ? $self->{option_results}->{proto} : 'https'; + $self->{api_username} = (defined($self->{option_results}->{api_username})) ? $self->{option_results}->{api_username} : undef; + $self->{api_password} = (defined($self->{option_results}->{api_password})) ? $self->{option_results}->{api_password} : undef; + $self->{realm} = (defined($self->{option_results}->{realm})) ? $self->{option_results}->{realm} : 'pam'; + + if (!defined($self->{hostname}) || $self->{hostname} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --hostname option."); + $self->{output}->option_exit(); + } + if (!defined($self->{api_username}) || $self->{api_username} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --api-username option."); + $self->{output}->option_exit(); + } + if (!defined($self->{api_password}) || $self->{api_password} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --api-password option."); + $self->{output}->option_exit(); + } + + $self->{cache}->check_options(option_results => $self->{option_results}); + + return 0; +} + +sub get_port { + my ($self, %options) = @_; + + return $self->{option_results}->{port}; +} + +sub get_hostnames { + my ($self, %options) = @_; + + return $self->{hostname}; +} + +sub build_options_for_httplib { + my ($self, %options) = @_; + + $self->{option_results}->{hostname} = $self->{hostname}; + $self->{option_results}->{port} = $self->{port}; + $self->{option_results}->{proto} = $self->{proto}; + $self->{option_results}->{ssl_opt} = $self->{ssl_opt}; + $self->{option_results}->{timeout} = $self->{timeout}; + $self->{option_results}->{proxyurl} = $self->{proxyurl}; + $self->{option_results}->{warning_status} = ''; + $self->{option_results}->{critical_status} = ''; + $self->{option_results}->{unknown_status} = ''; +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->add_header(key => 'Accept', value => 'application/json'); + $self->{http}->add_header(key => 'Content-Type', value => 'application/x-www-form-urlencoded'); + if (defined($self->{ticket})) { + $self->{http}->add_header(key => 'Cookie', value => 'PVEAuthCookie=' . $self->{ticket}); + } + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub get_ticket { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read(statefile => 'proxmox_ve_api_' . md5_hex($self->{option_results}->{hostname}) . '_' . md5_hex($self->{option_results}->{api_username})); + my $expires_on = $options{statefile}->get(name => 'expires_on'); + my $ticket = $options{statefile}->get(name => 'ticket'); + + if ($has_cache_file == 0 || !defined($ticket) || (($expires_on - time()) < 10)) { + my $post_data = 'username=' . $self->{api_username} . + '&password=' . $self->{api_password} . + '&realm=' . $self->{realm}; + + $self->settings(); + + my $content = $self->{http}->request(method => 'POST', query_form_post => $post_data, + url_path => '/api2/json/access/ticket'); + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->{output}->output_add(long_msg => $content, debug => 1); + $self->{output}->add_option_msg(short_msg => "Cannot decode json response"); + $self->{output}->option_exit(); + } + if (!defined($decoded->{data}->{ticket})) { + $self->{output}->output_add(long_msg => $decoded, debug => 1); + $self->{output}->add_option_msg(short_msg => "Error retrieving ticket"); + $self->{output}->option_exit(); + } + + $ticket = $decoded->{data}->{ticket}; + my $datas = { last_timestamp => time(), ticket => $ticket, expires_on => time() + 7200 }; + $options{statefile}->write(data => $datas); + } + + return $ticket; +} + +sub request_api { + my ($self, %options) = @_; + + if (!defined($self->{ticket})) { + $self->{ticket} = $self->get_ticket(statefile => $self->{cache}); + } + + $self->settings(); + + my $content = $self->{http}->request(%options); + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->{output}->output_add(long_msg => $content, debug => 1); + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); + $self->{output}->option_exit(); + } + if (!defined($decoded->{data})) { + $self->{output}->output_add(long_msg => $decoded, debug => 1); + $self->{output}->add_option_msg(short_msg => "Error while retrieving data (add --debug option for detailed message)"); + $self->{output}->option_exit(); + } + + return $decoded->{data}; +} + +sub get_version { + my ($self, %options) = @_; + + my $content = $self->request_api(method => 'GET', url_path =>'/api2/json/version'); + return $content->{version}; +} + +sub internal_api_list_vms { + my ($self, %options) = @_; + + my $vm = $self->request_api(method => 'GET', url_path =>'/api2/json/cluster/resources?type=vm'); + return $vm; +} + +sub api_list_vms { + my ($self, %options) = @_; + + my $vms = {}; + my $list_vms = $self->internal_api_list_vms(); + foreach my $vm (@{$list_vms}) { + my ($type, $vmid) = split(/\//,$vm->{id}); + $vms->{$vm->{id}} = { + State => $vm->{status}, + Type => $type, + Vmid => $vmid, + Node => $vm->{node}, + Name => $vm->{name}, + }; + } + + return $vms; +} + +sub internal_api_list_nodes { + my ($self, %options) = @_; + + my $nodes = $self->request_api(method => 'GET', url_path =>'/api2/json/cluster/resources?type=node'); + return $nodes; +} + +sub api_list_nodes { + my ($self, %options) = @_; + + my $nodes = {}; + my $list_nodes = $self->internal_api_list_nodes(); + foreach my $node (@{$list_nodes}) { + $nodes->{$node->{id}} = { + State => $node->{status}, + Name => $node->{node}, + }; + } + + return $nodes; +} + +sub internal_api_list_storages { + my ($self, %options) = @_; + + my $storage = $self->request_api(method => 'GET', url_path =>'/api2/json/cluster/resources?type=storage'); + return $storage; +} + +sub api_list_storages { + my ($self, %options) = @_; + + my $storages = {}; + my $list_storages = $self->internal_api_list_storages(); + foreach my $storage (@{$list_storages}) { + $storages->{$storage->{id}} = { + State => $storage->{status}, + Node => $storage->{node}, + Name => $storage->{storage}, + }; + } + + return $storages; +} + +sub cache_vms { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read(statefile => 'cache_proxmox_vm_'.$self->{hostname} . '_' . $self->{port}); + my $timestamp_cache = $options{statefile}->get(name => 'last_timestamp'); + my $vms = $options{statefile}->get(name => 'vms'); + if ($has_cache_file == 0 || !defined($timestamp_cache) || ((time() - $timestamp_cache) > (($options{reload_cache_time})))) { + $vms = {}; + my $list_vms = $self->internal_api_list_vms(); + foreach my $vm (@{$list_vms}) { + $vms->{$vm->{id}} = { + State => $vm->{status}, + Node => $vm->{node}, + Name => $vm->{name}, + }; + } + $options{statefile}->write(data => $vms); + } + + return $vms; +} + +sub cache_nodes { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read(statefile => 'cache_proxmox_node_'.$self->{hostname} . '_' . $self->{port}); + my $timestamp_cache = $options{statefile}->get(name => 'last_timestamp'); + my $nodes = $options{statefile}->get(name => 'nodes'); + if ($has_cache_file == 0 || !defined($timestamp_cache) || ((time() - $timestamp_cache) > (($options{reload_cache_time})))) { + $nodes = {}; + my $list_nodes = $self->internal_api_list_nodes(); + foreach my $node (@{$list_nodes}) { + $nodes->{$node->{id}} = { + State => $node->{status}, + Name => $node->{node}, + }; + } + $options{statefile}->write(data => $nodes); + } + + return $nodes; +} + +sub cache_storages { + my ($self, %options) = @_; + + my $has_cache_file = $options{statefile}->read(statefile => 'cache_proxmox_storage_'.$self->{hostname} . '_' . $self->{port}); + my $timestamp_cache = $options{statefile}->get(name => 'last_timestamp'); + my $storages = $options{statefile}->get(name => 'storages'); + if ($has_cache_file == 0 || !defined($timestamp_cache) || ((time() - $timestamp_cache) > (($options{reload_cache_time})))) { + $storages = {}; + my $list_storages = $self->internal_api_list_storages(); + foreach my $storage (@{$list_storages}) { + $storages->{$storage->{id}} = { + State => $storage->{status}, + Name => $storage->{storage}, + }; + } + $options{statefile}->write(data => $storages); + } + + return $storages; +} + +sub internal_api_get_vm_stats { + my ($self, %options) = @_; + + my $vm_stats = $self->request_api(method => 'GET', url_path => '/api2/json/nodes/' . $options{node_id} . '/' . $options{vm_id} . '/status/current'); + return $vm_stats; +} + +sub internal_api_get_node_stats { + my ($self, %options) = @_; + + my (undef, $node) = split(/\//, $options{node_id}); + + my $node_stats = $self->request_api(method => 'GET', url_path => '/api2/json/nodes/' . $node . '/status'); + return $node_stats; +} + +sub internal_api_get_storage_stats { + my ($self, %options) = @_; + + my (undef, $node, $storage) = split(/\//, $options{storage_id}); + + my $storage_stats = $self->request_api(method => 'GET', url_path => '/api2/json/nodes/' . $node . '/storage/' . $storage . '/status'); + return $storage_stats; +} + +sub internal_api_get_vm_node { + my ($self, %options) = @_; + + my $list_vms = $self->internal_api_list_vms(); + my $node = ''; + foreach my $vm (@{$list_vms}) { + if ($vm->{id} eq $options{vm_id}) { + $node = $vm->{node}; + last; + } + } + return $node; +} + +sub api_get_vms { + my ($self, %options) = @_; + + my $content_total = $self->cache_vms(statefile => $options{statefile}); + + if (defined($options{vm_id}) && $options{vm_id} ne '') { + if (defined($content_total->{$options{vm_id}})) { + $content_total->{$options{vm_id}}->{Stats} = $self->internal_api_get_vm_stats(node_id => $self->internal_api_get_vm_node(vm_id => $options{vm_id}), vm_id => $options{vm_id}); + } + } elsif (defined($options{vm_name}) && $options{vm_name} ne '') { + my $vm_id; + foreach (keys %{$content_total}) { + if ($content_total->{$_}->{Name} eq $options{vm_name}) { + $vm_id = $_; + last; + } + } + if (defined($vm_id)) { + $content_total->{$vm_id}->{Stats} = $self->internal_api_get_vm_stats(node_id => $content_total->{$vm_id}->{Node}, vm_id => $vm_id); + } + } else { + foreach my $vm_id (keys %{$content_total}) { + $content_total->{$vm_id}->{Stats} = $self->internal_api_get_vm_stats(node_id => $content_total->{$vm_id}->{Node}, vm_id => $vm_id); + } + } + + return $content_total; +} + +sub api_get_nodes { + my ($self, %options) = @_; + + my $content_total = $self->cache_nodes(statefile => $options{statefile}); + + if (defined($options{node_id}) && $options{node_id} ne '') { + if (defined($content_total->{$options{node_id}})) { + $content_total->{$options{node_id}}->{Stats} = $self->internal_api_get_node_stats(node_id => $options{node_id}); + } + } elsif (defined($options{node_name}) && $options{node_name} ne '') { + my $node_id; + foreach (keys %{$content_total}) { + if ($content_total->{$_}->{Name} eq $options{node_name}) { + $node_id = $_; + last; + } + } + if (defined($node_id)) { + $content_total->{$node_id}->{Stats} = $self->internal_api_get_node_stats(node_id => $node_id); + } + } else { + foreach my $node_id (keys %{$content_total}) { + $content_total->{$node_id}->{Stats} = $self->internal_api_get_node_stats(node_id => $node_id); + } + } + + return $content_total; +} + +sub api_get_storages { + my ($self, %options) = @_; + + my $content_total = $self->cache_storages(statefile => $options{statefile}); + + if (defined($options{storage_id}) && $options{storage_id} ne '') { + if (defined($content_total->{$options{storage_id}})) { + $content_total->{$options{storage_id}}->{Stats} = $self->internal_api_get_storage_stats(storage_id => $options{storage_id}); + } + } elsif (defined($options{storage_name}) && $options{storage_name} ne '') { + my $storage_id; + foreach (keys %{$content_total}) { + if ($content_total->{$_}->{Name} eq $options{storage_name}) { + $storage_id = $_; + last; + } + } + if (defined($storage_id)) { + $content_total->{$storage_id}->{Stats} = $self->internal_api_get_storage_stats(storage_id => $storage_id); + } + } else { + foreach my $storage_id (keys %{$content_total}) { + $content_total->{$storage_id}->{Stats} = $self->internal_api_get_storage_stats(storage_id => $storage_id); + } + } + + return $content_total; +} + +1; + +__END__ + +=head1 NAME + +Proxmox VE Rest API + +=head1 REST API OPTIONS + +Proxmox Rest API + +More Info about Proxmox VE API on https://pve.proxmox.com/wiki/Proxmox_VE_API + +=over 8 + +=item B<--hostname> + +Set hostname or IP of Proxmox VE Cluster node + +=item B<--port> + +Set Proxmox VE Port (Default: '8006') + +=item B<--proto> + +Specify https if needed (Default: 'https'). + +=item B<--api-username> + +Set Proxmox VE Username +API user need to have this privileges +'VM.Monitor, VM.Audit, Datastore.Audit, Sys.Audit, Sys.Syslog' + +=item B<--api-password> + +Set Proxmox VE Password + +=item B<--realm> + +Set Proxmox VE Realm (pam, pve or custom) (Default: 'pam'). + +=item B<--proxyurl> + +Proxy URL if any. + +=item B<--proxypac> + +Proxy pac file (can be an url or local file). + +=item B<--timeout> + +Threshold for HTTP timeout. + +=item B<--ssl-opt> + +Set SSL Options (--ssl-opt="SSL_version => TLSv1" --ssl-opt="SSL_verify_mode => SSL_VERIFY_NONE"). + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/listnodes.pm b/apps/proxmox/ve/restapi/mode/listnodes.pm new file mode 100644 index 000000000..9d292d27c --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/listnodes.pm @@ -0,0 +1,99 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::listnodes; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + }); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{nodes} = $options{custom}->api_list_nodes(); +} + +sub run { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $node_id (sort keys %{$self->{nodes}}) { + $self->{output}->output_add(long_msg => '[id = ' . $node_id . "] " . + " [name = '" . $self->{nodes}->{$node_id}->{Name} . "']" . + " [state = '" . $self->{nodes}->{$node_id}->{State} . "']" + ); + } + + $self->{output}->output_add(severity => 'OK', + short_msg => 'List Nodes:'); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); +} + +sub disco_format { + my ($self, %options) = @_; + + $self->{output}->add_disco_format(elements => ['id', 'name', 'state']); +} + +sub disco_show { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $node_id (sort keys %{$self->{nodes}}) { + $self->{output}->add_disco_entry( + name => $self->{nodes}->{$node_id}->{Name}, + status => $self->{nodes}->{$node_id}->{State}, + id => $node_id, + ); + } +} + +1; + +__END__ + +=head1 MODE + +List nodes + +=over 8 + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/liststorages.pm b/apps/proxmox/ve/restapi/mode/liststorages.pm new file mode 100644 index 000000000..e33508c07 --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/liststorages.pm @@ -0,0 +1,101 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::liststorages; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + }); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{storages} = $options{custom}->api_list_storages(); +} + +sub run { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $storage_id (sort keys %{$self->{storages}}) { + $self->{output}->output_add(long_msg => '[id = ' . $storage_id . "] " . + " [name = '" . $self->{storages}->{$storage_id}->{Name} . "']" . + " [node = '" . $self->{storages}->{$storage_id}->{Node} . "']" . + " [state = '" . $self->{storages}->{$storage_id}->{State} . "']" + ); + } + + $self->{output}->output_add(severity => 'OK', + short_msg => 'List Storages:'); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); +} + +sub disco_format { + my ($self, %options) = @_; + + $self->{output}->add_disco_format(elements => ['id', 'name', 'node', 'status']); +} + +sub disco_show { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $storage_id (sort keys %{$self->{storages}}) { + $self->{output}->add_disco_entry( + name => $self->{storages}->{$storage_id}->{Name}, + node => $self->{storages}->{$storage_id}->{Node}, + state => $self->{storages}->{$storage_id}->{State}, + id => $storage_id, + ); + } +} + +1; + +__END__ + +=head1 MODE + +List storages + +=over 8 + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/listvms.pm b/apps/proxmox/ve/restapi/mode/listvms.pm new file mode 100644 index 000000000..e486d2162 --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/listvms.pm @@ -0,0 +1,104 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::listvms; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + }); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{vms} = $options{custom}->api_list_vms(); +} + +sub run { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $vm_id (sort keys %{$self->{vms}}) { + $self->{output}->output_add(long_msg => '[id = ' . $vm_id . "] [name = '" . $self->{vms}->{$vm_id}->{Name} . "']" . + " [node = '" . $self->{vms}->{$vm_id}->{Node} . "']" . + " [state = '" . $self->{vms}->{$vm_id}->{State} . "']" . + " [vmid = '" . $self->{vms}->{$vm_id}->{Vmid} . "']" . + " [type = '" . $self->{vms}->{$vm_id}->{Type} . "']" + ); + } + + $self->{output}->output_add(severity => 'OK', + short_msg => 'List VMs:'); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); +} + +sub disco_format { + my ($self, %options) = @_; + + $self->{output}->add_disco_format(elements => ['id', 'name', 'node' ,'status','type','vmid']); +} + +sub disco_show { + my ($self, %options) = @_; + + $self->manage_selection(%options); + foreach my $vm_id (sort keys %{$self->{vms}}) { + $self->{output}->add_disco_entry( + name => $self->{vms}->{$vm_id}->{Name}, + node => $self->{vms}->{$vm_id}->{Node}, + state => $self->{vms}->{$vm_id}->{State}, + id => $vm_id, + type => $self->{vms}->{$vm_id}->{Type}, + vmid =>$self->{vms}->{$vm_id}->{Vmid}, + ); + } +} + +1; + +__END__ + +=head1 MODE + +List VMs + +=over 8 + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/nodeusage.pm b/apps/proxmox/ve/restapi/mode/nodeusage.pm new file mode 100644 index 000000000..0437b1922 --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/nodeusage.pm @@ -0,0 +1,426 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::nodeusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); + +my $instance_mode; + +sub custom_status_threshold { + my ($self, %options) = @_; + my $status = 'ok'; + my $message; + + eval { + local $SIG{__WARN__} = sub { $message = $_[0]; }; + local $SIG{__DIE__} = sub { $message = $_[0]; }; + + if (defined($instance_mode->{option_results}->{critical_node_status}) && $instance_mode->{option_results}->{critical_node_status} ne '' && + eval "$instance_mode->{option_results}->{critical_node_status}") { + $status = 'critical'; + } elsif (defined($instance_mode->{option_results}->{warning_node_status}) && $instance_mode->{option_results}->{warning_node_status} ne '' && + eval "$instance_mode->{option_results}->{warning_node_status}") { + $status = 'warning'; + } + }; + if (defined($message)) { + $self->{output}->output_add(long_msg => 'filter status issue: ' . $message); + } + + return $status; +} + +sub custom_status_output { + my ($self, %options) = @_; + my $msg = 'state : ' . $self->{result_values}->{state}; + + return $msg; +} + +sub custom_status_calc { + my ($self, %options) = @_; + + $self->{result_values}->{state} = $options{new_datas}->{$self->{instance} . '_state'}; + $self->{result_values}->{name} = $options{new_datas}->{$self->{instance} . '_name'}; + + return 0; +} + +sub custom_cpu_calc { + my ($self, %options) = @_; + + my $delta_cpu_total = $options{new_datas}->{$self->{instance} . '_cpu_total_usage'} - $options{old_datas}->{$self->{instance} . '_cpu_total_usage'}; + $self->{result_values}->{prct_cpu} = (($delta_cpu_total) * 100); + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + + return 0; +} + +sub custom_memory_perfdata { + my ($self, %options) = @_; + + my $extra_label = ''; + if (!defined($options{extra_instance}) || $options{extra_instance} != 0) { + $extra_label .= '_' . $self->{result_values}->{display}; + } + $self->{output}->perfdata_add(label => 'memory_used' . $extra_label, unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_memory_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, + threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_memory_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Memory Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_memory_calc { + my ($self, %options) = @_; + + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_memory_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_memory_usage'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_free} = $self->{result_values}->{free} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + return 0; +} + +sub custom_swap_perfdata { + my ($self, %options) = @_; + + my $extra_label = ''; + if (!defined($options{extra_instance}) || $options{extra_instance} != 0) { + $extra_label .= '_' . $self->{result_values}->{display}; + } + $self->{output}->perfdata_add(label => 'swap_used' . $extra_label, unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_swap_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, + threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_swap_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Swap Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_swap_calc { + my ($self, %options) = @_; + + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_swap_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_swap_usage'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_free} = $self->{result_values}->{free} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + return 0; +} + +sub custom_fs_perfdata { + my ($self, %options) = @_; + + my $extra_label = ''; + if (!defined($options{extra_instance}) || $options{extra_instance} != 0) { + $extra_label .= '_' . $self->{result_values}->{display}; + } + $self->{output}->perfdata_add(label => 'fs_used' . $extra_label, unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_fs_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, + threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_fs_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Root Filesystem Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_fs_calc { + my ($self, %options) = @_; + + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_fs_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_fs_usage'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_free} = $self->{result_values}->{free} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + return 0; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'nodes', type => 1, cb_prefix_output => 'prefix_nodes_output', + message_multiple => 'All nodes are ok', skipped_code => { -11 => 1 } }, + ]; + + $self->{maps_counters}->{nodes} = [ + { label => 'node-status', threshold => 0, set => { + key_values => [ { name => 'state' }, { name => 'name' } ], + closure_custom_calc => $self->can('custom_status_calc'), + closure_custom_output => $self->can('custom_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => $self->can('custom_status_threshold'), + } + }, + { label => 'cpu', set => { + key_values => [ { name => 'cpu_total_usage', diff => 1 }, { name => 'cpu_number' }, { name => 'display' } ], + output_template => 'CPU Usage : %.2f %%', + closure_custom_calc => $self->can('custom_cpu_calc'), + output_use => 'prct_cpu', threshold_use => 'prct_cpu', + perfdatas => [ + { label => 'cpu', value => 'prct_cpu', template => '%.2f', + unit => '%', min => 0, max => 100, label_extra_instance => 1, instance_use => 'display' }, + ], + } + }, + { label => 'memory', set => { + key_values => [ { name => 'memory_usage' }, { name => 'memory_total' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_memory_calc'), + closure_custom_output => $self->can('custom_memory_output'), + closure_custom_perfdata => $self->can('custom_memory_perfdata'), + closure_custom_threshold_check => $self->can('custom_memory_threshold'), + } + }, + { label => 'fs', set => { + key_values => [ { name => 'fs_usage' }, { name => 'fs_total' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_fs_calc'), + closure_custom_output => $self->can('custom_fs_output'), + closure_custom_perfdata => $self->can('custom_fs_perfdata'), + closure_custom_threshold_check => $self->can('custom_fs_threshold'), + } + }, + { label => 'swap', set => { + key_values => [ { name => 'swap_usage' }, { name => 'swap_total' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_swap_calc'), + closure_custom_output => $self->can('custom_swap_output'), + closure_custom_perfdata => $self->can('custom_swap_perfdata'), + closure_custom_threshold_check => $self->can('custom_swap_threshold'), + } + }, + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + "node-id:s" => { name => 'node_id' }, + "node-name:s" => { name => 'node_name' }, + "filter-name:s" => { name => 'filter_name' }, + "use-name" => { name => 'use_name' }, + "warning-node-status:s" => { name => 'warning_node_status', default => '' }, + "critical-node-status:s" => { name => 'critical_node_status', default => '' }, + }); + + $self->{statefile_cache_nodes} = centreon::plugins::statefile->new(%options); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + $instance_mode = $self; + $self->change_macros(); + $self->{statefile_cache_nodes}->check_options(%options); +} + +sub prefix_nodes_output { + my ($self, %options) = @_; + + return "Node '" . $options{instance_value}->{display} . "' "; +} + +sub change_macros { + my ($self, %options) = @_; + + foreach (('warning_node_status', 'critical_node_status')) { + if (defined($self->{option_results}->{$_})) { + $self->{option_results}->{$_} =~ s/%\{(.*?)\}/\$self->{result_values}->{$1}/g; + } + } +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{nodes} = {}; + + my $result = $options{custom}->api_get_nodes( + node_id => $self->{option_results}->{node_id}, + node_name => $self->{option_results}->{node_name}, + statefile => $self->{statefile_cache_nodes} + ); + + foreach my $node_id (keys %{$result}) { + next if (!defined($result->{$node_id}->{Stats})); + + my $name = $result->{$node_id}->{Name}; + if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && + $name !~ /$self->{option_results}->{filter_name}/) { + $self->{output}->output_add(long_msg => "skipping '" . $name . "': no matching filter.", debug => 1); + next; + } + $self->{nodes}->{$node_id} = { + display => defined($self->{option_results}->{use_name}) ? $name : $node_id, + name => $name, + state => $result->{$node_id}->{State}, + cpu_total_usage => $result->{$node_id}->{Stats}->{cpu}, + cpu_number => $result->{$node_id}->{Stats}->{cpuinfo}->{cpus}, + memory_usage => $result->{$node_id}->{Stats}->{memory}->{used}, + memory_total => $result->{$node_id}->{Stats}->{memory}->{total}, + swap_usage => $result->{$node_id}->{Stats}->{swap}->{used}, + swap_total => $result->{$node_id}->{Stats}->{swap}->{total}, + fs_usage => $result->{$node_id}->{Stats}->{rootfs}->{used}, + fs_total => $result->{$node_id}->{Stats}->{rootfs}->{total}, + }; + } + + if (scalar(keys %{$self->{nodes}}) <= 0) { + $self->{output}->add_option_msg(short_msg => "No node found."); + $self->{output}->option_exit(); + } + + my $hostnames = $options{custom}->get_hostnames(); + $self->{cache_name} = "proxmox_" . $self->{mode} . '_' .$hostnames . '_' . $options{custom}->get_port() . '_' . + (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{filter_name}) ? md5_hex($self->{option_results}->{filter_name}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{node_id}) ? md5_hex($self->{option_results}->{node_id}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{node_name}) ? md5_hex($self->{option_results}->{node_name}) : md5_hex('all')); +} + +1; + +__END__ + +=head1 MODE + +Check node usage. + +=over 8 + +=item B<--node-id> + +Exact node ID. + +=item B<--node-name> + +Exact node name (if multiple names: names separated by ':'). + +=item B<--use-name> + +Use node name for perfdata and display. + +=item B<--filter-name> + +Filter by node name (can be a regexp). + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='^node-status$' + +=item B<--warning-*> + +Threshold warning. +Can be: 'cpu' (%), 'memory' (%), 'swap' (%), 'fs' (%). + +=item B<--critical-*> + +Threshold critical. +Can be: 'cpu' (%), 'memory' (%), 'swap' (%), 'fs' (%). + +=item B<--warning-node-status> + +Set warning threshold for status (Default: -) +Can used special variables like: %{name}, %{state}. + +=item B<--critical-node-status> + +Set critical threshold for status (Default: -). +Can used special variables like: %{name}, %{state}. + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/storageusage.pm b/apps/proxmox/ve/restapi/mode/storageusage.pm new file mode 100644 index 000000000..99a694fb7 --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/storageusage.pm @@ -0,0 +1,285 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::storageusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); + +my $instance_mode; + +sub custom_status_threshold { + my ($self, %options) = @_; + my $status = 'ok'; + my $message; + + eval { + local $SIG{__WARN__} = sub { $message = $_[0]; }; + local $SIG{__DIE__} = sub { $message = $_[0]; }; + + if (defined($instance_mode->{option_results}->{critical_storage_status}) && $instance_mode->{option_results}->{critical_storage_status} ne '' && + eval "$instance_mode->{option_results}->{critical_storage_status}") { + $status = 'critical'; + } elsif (defined($instance_mode->{option_results}->{warning_storage_status}) && $instance_mode->{option_results}->{warning_storage_status} ne '' && + eval "$instance_mode->{option_results}->{warning_storage_status}") { + $status = 'warning'; + } + }; + if (defined($message)) { + $self->{output}->output_add(long_msg => 'filter status issue: ' . $message); + } + + return $status; +} + +sub custom_status_output { + my ($self, %options) = @_; + my $msg = 'state : ' . $self->{result_values}->{state}; + + return $msg; +} + +sub custom_status_calc { + my ($self, %options) = @_; + + $self->{result_values}->{state} = $options{new_datas}->{$self->{instance} . '_state'}; + $self->{result_values}->{name} = $options{new_datas}->{$self->{instance} . '_name'}; + + return 0; +} + +sub custom_storage_perfdata { + my ($self, %options) = @_; + + my $extra_label = ''; + if (!defined($options{extra_instance}) || $options{extra_instance} != 0) { + $extra_label .= '_' . $self->{result_values}->{display}; + } + $self->{output}->perfdata_add(label => 'storage_used' . $extra_label, unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_disc_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, + threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_storage_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Storage Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_storage_calc { + my ($self, %options) = @_; + + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_storage_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_storage_usage'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_free} = $self->{result_values}->{free} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + return 0; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'storages', type => 1, cb_prefix_output => 'prefix_storages_output', + message_multiple => 'All storages are ok', skipped_code => { -11 => 1 } }, + ]; + + $self->{maps_counters}->{storages} = [ + { label => 'storage-status', threshold => 0, set => { + key_values => [ { name => 'state' }, { name => 'name' } ], + closure_custom_calc => $self->can('custom_status_calc'), + closure_custom_output => $self->can('custom_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => $self->can('custom_status_threshold'), + } + }, + { label => 'storage', set => { + key_values => [ { name => 'storage_usage' }, { name => 'storage_total' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_storage_calc'), + closure_custom_output => $self->can('custom_storage_output'), + closure_custom_perfdata => $self->can('custom_storage_perfdata'), + closure_custom_threshold_check => $self->can('custom_storage_threshold'), + } + }, + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + "storage-id:s" => { name => 'storage_id' }, + "storage-name:s" => { name => 'storage_name' }, + "filter-name:s" => { name => 'filter_name' }, + "use-name" => { name => 'use_name' }, + "warning-storage-status:s" => { name => 'warning_storage_status', default => '' }, + "critical-storage-status:s" => { name => 'critical_storage_status', default => '' }, + }); + + $self->{statefile_cache_storages} = centreon::plugins::statefile->new(%options); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + $instance_mode = $self; + $self->change_macros(); + $self->{statefile_cache_storages}->check_options(%options); +} + +sub prefix_storages_output { + my ($self, %options) = @_; + + return "Storage '" . $options{instance_value}->{display} . "' "; +} + +sub change_macros { + my ($self, %options) = @_; + + foreach (('warning_storage_status', 'critical_storage_status')) { + if (defined($self->{option_results}->{$_})) { + $self->{option_results}->{$_} =~ s/%\{(.*?)\}/\$self->{result_values}->{$1}/g; + } + } +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{storages} = {}; + + my $result = $options{custom}->api_get_storages( + storage_id => $self->{option_results}->{storage_id}, + storage_name => $self->{option_results}->{storage_name}, + statefile => $self->{statefile_cache_storages} + ); + + foreach my $storage_id (keys %{$result}) { + next if (!defined($result->{$storage_id}->{Stats})); + + my $name = $result->{$storage_id}->{Name}; + if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && + $name !~ /$self->{option_results}->{filter_name}/) { + $self->{output}->output_add(long_msg => "skipping '" . $name . "': no matching filter.", debug => 1); + next; + } + $self->{storages}->{$storage_id} = { + display => defined($self->{option_results}->{use_name}) ? $name : $storage_id, + name => $name, + state => $result->{$storage_id}->{State}, + storage_usage => $result->{$storage_id}->{Stats}->{used}, + storage_total => $result->{$storage_id}->{Stats}->{total}, + }; + } + + if (scalar(keys %{$self->{storages}}) <= 0) { + $self->{output}->add_option_msg(short_msg => "No storage found."); + $self->{output}->option_exit(); + } + + my $hostnames = $options{custom}->get_hostnames(); + $self->{cache_name} = "proxmox_" . $self->{mode} . '_' .$hostnames . '_' . $options{custom}->get_port() . '_' . + (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{filter_name}) ? md5_hex($self->{option_results}->{filter_name}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{storage_id}) ? md5_hex($self->{option_results}->{storage_id}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{storage_name}) ? md5_hex($self->{option_results}->{storage_name}) : md5_hex('all')); +} + +1; + +=head1 MODE + +Check storage usage. + +=over 8 + +=item B<--storage-id> + +Exact storage ID. + +=item B<--storage-name> + +Exact storage name (if multiple names: names separated by ':'). + +=item B<--use-name> + +Use storage name for perfdata and display. + +=item B<--filter-name> + +Filter by storage name (can be a regexp). + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='^storage-status$' + +=item B<--warning-*> + +Threshold warning. +Can be: 'storage' (%). + +=item B<--critical-*> + +Threshold critical. +Can be: 'storage' (%). + +=item B<--warning-storage-status> + +Set warning threshold for status (Default: -) +Can used special variables like: %{name}, %{state}. + +=item B<--critical-storage-status> + +Set critical threshold for status (Default: -). +Can used special variables like: %{name}, %{state}. + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/version.pm b/apps/proxmox/ve/restapi/mode/version.pm new file mode 100644 index 000000000..4a5ca429b --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/version.pm @@ -0,0 +1,64 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::version; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + }); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); +} + +sub run { + my ($self, %options) = @_; + my $result = $options{custom}->get_version(); + $self->{output}->output_add(severity => 'OK', + short_msg => "Version is '" . $result . "'"); + $self->{output}->display(nolabel => 1, force_ignore_perfdata => 1, force_long_output => 1); + $self->{output}->exit(); +} + +1; + +=head1 MODE + +Get Proxmox VE Version + +=over 8 + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/mode/vmusage.pm b/apps/proxmox/ve/restapi/mode/vmusage.pm new file mode 100644 index 000000000..4b547d142 --- /dev/null +++ b/apps/proxmox/ve/restapi/mode/vmusage.pm @@ -0,0 +1,369 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::mode::vmusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use Digest::MD5 qw(md5_hex); + +my $instance_mode; + +sub custom_status_threshold { + my ($self, %options) = @_; + my $status = 'ok'; + my $message; + + eval { + local $SIG{__WARN__} = sub { $message = $_[0]; }; + local $SIG{__DIE__} = sub { $message = $_[0]; }; + + if (defined($instance_mode->{option_results}->{critical_vm_status}) && $instance_mode->{option_results}->{critical_vm_status} ne '' && + eval "$instance_mode->{option_results}->{critical_vm_status}") { + $status = 'critical'; + } elsif (defined($instance_mode->{option_results}->{warning_vm_status}) && $instance_mode->{option_results}->{warning_vm_status} ne '' && + eval "$instance_mode->{option_results}->{warning_vm_status}") { + $status = 'warning'; + } + }; + if (defined($message)) { + $self->{output}->output_add(long_msg => 'filter status issue: ' . $message); + } + + return $status; +} + +sub custom_status_output { + my ($self, %options) = @_; + my $msg = 'state : ' . $self->{result_values}->{state}; + + return $msg; +} + +sub custom_status_calc { + my ($self, %options) = @_; + + $self->{result_values}->{state} = $options{new_datas}->{$self->{instance} . '_state'}; + $self->{result_values}->{name} = $options{new_datas}->{$self->{instance} . '_name'}; + + return 0; +} + +sub custom_cpu_calc { + my ($self, %options) = @_; + + my $delta_cpu_total = $options{new_datas}->{$self->{instance} . '_cpu_total_usage'} - $options{old_datas}->{$self->{instance} . '_cpu_total_usage'}; + $self->{result_values}->{prct_cpu} = $delta_cpu_total * 100; + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + + return 0; +} + +sub custom_memory_perfdata { + my ($self, %options) = @_; + + my $extra_label = ''; + if (!defined($options{extra_instance}) || $options{extra_instance} != 0) { + $extra_label .= '_' . $self->{result_values}->{display}; + } + $self->{output}->perfdata_add(label => 'memory_used' . $extra_label, unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, total => $self->{result_values}->{total}, cast_int => 1), + min => 0, max => $self->{result_values}->{total}); +} + +sub custom_memory_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, + threshold => [ { label => 'critical-' . $self->{label}, exit_litteral => 'critical' }, { label => 'warning-' . $self->{label}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_memory_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + + my $msg = sprintf("Memory Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)", + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free}); + return $msg; +} + +sub custom_memory_calc { + my ($self, %options) = @_; + + $self->{result_values}->{display} = $options{new_datas}->{$self->{instance} . '_display'}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_memory_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_memory_usage'}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_free} = $self->{result_values}->{free} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + return 0; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'vms', type => 1, cb_prefix_output => 'prefix_vms_output', message_multiple => 'All vms are ok', skipped_code => { -11 => 1 } }, + { name => 'vms_traffic', type => 1, cb_prefix_output => 'prefix_vms_traffic_output', message_multiple => 'All vm traffics are ok', skipped_code => { -11 => 1 } }, + ]; + + $self->{maps_counters}->{vms} = [ + { label => 'vm-status', threshold => 0, set => { + key_values => [ { name => 'state' }, { name => 'name' } ], + closure_custom_calc => $self->can('custom_status_calc'), + closure_custom_output => $self->can('custom_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => $self->can('custom_status_threshold'), + } + }, + { label => 'cpu', set => { + key_values => [ { name => 'cpu_total_usage', diff => 1 }, { name => 'cpu_number' }, { name => 'display' } ], + output_template => 'CPU Usage : %.2f %%', + closure_custom_calc => $self->can('custom_cpu_calc'), + output_use => 'prct_cpu', threshold_use => 'prct_cpu', + perfdatas => [ + { label => 'cpu', value => 'prct_cpu', template => '%.2f', + unit => '%', min => 0, max => 100, label_extra_instance => 1, instance_use => 'display' }, + ], + } + }, + { label => 'memory', set => { + key_values => [ { name => 'memory_usage' }, { name => 'memory_total' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_memory_calc'), + closure_custom_output => $self->can('custom_memory_output'), + closure_custom_perfdata => $self->can('custom_memory_perfdata'), + closure_custom_threshold_check => $self->can('custom_memory_threshold'), + } + }, + { label => 'read-iops', set => { + key_values => [ { name => 'read_io', diff => 1 }, { name => 'display' } ], + per_second => 1, + output_template => 'Read IOPs : %.2f', output_error_template => "Read IOPs : %s", + perfdatas => [ + { label => 'read_iops', value => 'read_io_per_second', template => '%.2f', + unit => 'iops', min => 0, label_extra_instance => 1, instance_use => 'display_absolute' }, + ], + } + }, + { label => 'write-iops', set => { + key_values => [ { name => 'write_io', diff => 1 }, { name => 'display' } ], + per_second => 1, + output_template => 'Write IOPs : %.2f', output_error_template => "Write IOPs : %s", + perfdatas => [ + { label => 'write_iops', value => 'write_io_per_second', template => '%.2f', + unit => 'iops', min => 0, label_extra_instance => 1, instance_use => 'display_absolute' }, + ], + } + }, + ]; + + $self->{maps_counters}->{vms_traffic} = [ + { label => 'traffic-in', set => { + key_values => [ { name => 'traffic_in', diff => 1 }, { name => 'display' } ], + per_second => 1, output_change_bytes => 2, + output_template => 'Traffic In : %s %s/s', + perfdatas => [ + { label => 'traffic_in', value => 'traffic_in_per_second', template => '%.2f', + min => 0, unit => 'b/s', label_extra_instance => 1, instance_use => 'display_absolute' }, + ], + } + }, + { label => 'traffic-out', set => { + key_values => [ { name => 'traffic_out', diff => 1 }, { name => 'display' } ], + per_second => 1, output_change_bytes => 2, + output_template => 'Traffic Out : %s %s/s', + perfdatas => [ + { label => 'traffic_out', value => 'traffic_out_per_second', template => '%.2f', + min => 0, unit => 'b/s', label_extra_instance => 1, instance_use => 'display_absolute' }, + ], + } + }, + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, statefile => 1); + bless $self, $class; + + $self->{version} = '1.0'; + $options{options}->add_options(arguments => + { + "vm-id:s" => { name => 'vm_id' }, + "vm-name:s" => { name => 'vm_name' }, + "filter-name:s" => { name => 'filter_name' }, + "use-name" => { name => 'use_name' }, + "warning-vm-status:s" => { name => 'warning_vm_status', default => '' }, + "critical-vm-status:s" => { name => 'critical_vm_status', default => '' }, + }); + $self->{statefile_cache_vms} = centreon::plugins::statefile->new(%options); + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + $instance_mode = $self; + $self->change_macros(); + $self->{statefile_cache_vms}->check_options(%options); +} + +sub prefix_vms_traffic_output { + my ($self, %options) = @_; + + return "VM '" . $options{instance_value}->{display} . "' "; +} + +sub prefix_vms_output { + my ($self, %options) = @_; + + return "VM '" . $options{instance_value}->{display} . "' "; +} + +sub change_macros { + my ($self, %options) = @_; + + foreach (('warning_vm_status', 'critical_vm_status')) { + if (defined($self->{option_results}->{$_})) { + $self->{option_results}->{$_} =~ s/%\{(.*?)\}/\$self->{result_values}->{$1}/g; + } + } +} + +sub manage_selection { + my ($self, %options) = @_; + + $self->{vms} = {}; + $self->{vms_traffic} = {}; + + my $result = $options{custom}->api_get_vms( + vm_id => $self->{option_results}->{vm_id}, + vm_name => $self->{option_results}->{vm_name}, + statefile => $self->{statefile_cache_vms} + ); + + foreach my $vm_id (keys %{$result}) { + next if (!defined($result->{$vm_id}->{Stats})); + + my $name = $result->{$vm_id}->{Name}; + if (defined($self->{option_results}->{filter_name}) && $self->{option_results}->{filter_name} ne '' && + $name !~ /$self->{option_results}->{filter_name}/) { + $self->{output}->output_add(long_msg => "skipping '" . $name . "': no matching filter.", debug => 1); + next; + } + + $self->{vms}->{$vm_id} = { + display => defined($self->{option_results}->{use_name}) ? $name : $vm_id, + name => $name, + state => $result->{$vm_id}->{State}, + read_io => $result->{$vm_id}->{Stats}->{diskread}, + write_io => $result->{$vm_id}->{Stats}->{diskwrite}, + cpu_total_usage => $result->{$vm_id}->{Stats}->{cpu}, + cpu_number => $result->{$vm_id}->{Stats}->{cpus}, + memory_usage => $result->{$vm_id}->{Stats}->{mem}, + memory_total => $result->{$vm_id}->{Stats}->{maxmem}, + swap_usage => $result->{$vm_id}->{Stats}->{swap}, + swap_total => $result->{$vm_id}->{Stats}->{maxswap}, + }; + $self->{vms_traffic}->{$name} = { + display => $name, + traffic_in => $result->{$vm_id}->{Stats}->{netin} * 8, + traffic_out => $result->{$vm_id}->{Stats}->{netout} * 8 + }; + } + + if (scalar(keys %{$self->{vms}}) <= 0) { + $self->{output}->add_option_msg(short_msg => "No vm found."); + $self->{output}->option_exit(); + } + + my $hostnames = $options{custom}->get_hostnames(); + $self->{cache_name} = "proxmox_" . $self->{mode} . '_' .$hostnames . '_' . $options{custom}->get_port() . '_' . + (defined($self->{option_results}->{filter_counters}) ? md5_hex($self->{option_results}->{filter_counters}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{filter_name}) ? md5_hex($self->{option_results}->{filter_name}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{vm_id}) ? md5_hex($self->{option_results}->{vm_id}) : md5_hex('all')) . '_' . + (defined($self->{option_results}->{vm_name}) ? md5_hex($self->{option_results}->{vm_name}) : md5_hex('all')); +} + +1; + +=head1 MODE + +Check VM usage on Proxmox VE Cluster. + +=over 8 + +=item B<--vm-id> + +Exact VM ID. + +=item B<--vm-name> + +Exact VM name (if multiple names: names separated by ':'). + +=item B<--use-name> + +Use VM name for perfdata and display. + +=item B<--filter-name> + +Filter by vm name (can be a regexp). + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='^vm-status$' + +=item B<--warning-*> + +Threshold warning. +Can be: 'read-iops', 'write-iops', 'traffic-in', 'traffic-out', +'cpu' (%), 'memory' (%). + +=item B<--critical-*> + +Threshold critical. +Can be: 'read-iops', 'write-iops', 'traffic-in', 'traffic-out', +'cpu' (%), 'memory' (%). + +=item B<--warning-vm-status> + +Set warning threshold for status (Default: -) +Can used special variables like: %{name}, %{state}. + +=item B<--critical-vm-status> + +Set critical threshold for status (Default: -). +Can used special variables like: %{name}, %{state}. + +=back + +=cut diff --git a/apps/proxmox/ve/restapi/plugin.pm b/apps/proxmox/ve/restapi/plugin.pm new file mode 100644 index 000000000..d2701954a --- /dev/null +++ b/apps/proxmox/ve/restapi/plugin.pm @@ -0,0 +1,71 @@ +# +# Copyright 2018 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package apps::proxmox::ve::restapi::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_custom); + +sub new { + my ($class, %options) = @_; + + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + %{$self->{modes}} = ( + 'list-nodes' => 'apps::proxmox::ve::restapi::mode::listnodes', + 'list-storages' => 'apps::proxmox::ve::restapi::mode::liststorages', + 'list-vms' => 'apps::proxmox::ve::restapi::mode::listvms', + 'storage-usage' => 'apps::proxmox::ve::restapi::mode::storageusage', + 'node-usage' => 'apps::proxmox::ve::restapi::mode::nodeusage', + 'version' => 'apps::proxmox::ve::restapi::mode::version', + 'vm-usage' => 'apps::proxmox::ve::restapi::mode::vmusage', + ); + + $self->{custom_modes}{api} = 'apps::proxmox::ve::restapi::custom::api'; + + return $self; +} + +sub init { + my ($self, %options) = @_; + + $self->SUPER::init(%options); +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Proxmox VE ressources through its HTTPS remote API. + +API documentation can be checked at https://pve.proxmox.com/pve-docs/api-viewer/. + +Requirements: Proxmox VE 5.x. + +=over 8 + +=back + +=cut