diff --git a/src/apps/proxmox/ve/restapi/custom/api.pm b/src/apps/proxmox/ve/restapi/custom/api.pm index 872aebb3a..90c474bb8 100644 --- a/src/apps/proxmox/ve/restapi/custom/api.pm +++ b/src/apps/proxmox/ve/restapi/custom/api.pm @@ -26,6 +26,7 @@ use strict; use warnings; use centreon::plugins::http; use centreon::plugins::statefile; +use centreon::plugins::misc; use JSON::XS; use Digest::MD5 qw(md5_hex); @@ -149,7 +150,7 @@ sub get_ticket { my $post_data = 'username=' . $self->{api_username} . '&password=' . $self->{api_password} . '&realm=' . $self->{realm}; - + $self->settings(); my $content = $self->{http}->request( @@ -186,20 +187,21 @@ sub request_api { 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 ($@) { + # When silently_fail is set we ignore errors + return undef if $options{silently_fail}; + $self->{output}->add_option_msg(short_msg => "Cannot decode json response: $@"); $self->{output}->option_exit(); } - if (!defined($decoded->{data})) { + if (!exists($decoded->{data})) { $self->{output}->add_option_msg(short_msg => "Error while retrieving data (add --debug option for detailed message)"); $self->{output}->option_exit(); } @@ -216,7 +218,7 @@ sub get_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; } @@ -242,7 +244,7 @@ sub api_list_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; } @@ -350,6 +352,93 @@ sub cache_storages { return $storages; } +sub internal_api_get_network_interfaces { + my ($self, %options) = @_; + + # We use the silently_fail option because we don't want to crash the module when QEMU agent is not running + # In this case we simply ignore the IP retrieval + my $vm_network = $self->request_api(method => 'GET', + url_path => '/api2/json/nodes/' . $options{node_id} . '/qemu/' . $options{vm_id} . '/agent/network-get-interfaces', + silently_fail => 1); + + return $vm_network; +} + +sub internal_api_get_osinfo { + my ($self, %options) = @_; + + # We use the silently_fail option because we don't want to crash the module when QEMU agent is not running + # In this case we simply ignore the osinfo retrieval + my $vm_osinfo = $self->request_api(method => 'GET', + url_path => '/api2/json/nodes/' . $options{node_id} . '/qemu/' . $options{vm_id} . '/agent/get-osinfo', + silently_fail => 1); + return $vm_osinfo; +} + +sub api_get_osinfo { + my ($self, %options) = @_; + + my $vm_osinfo = $self->internal_api_get_osinfo(node_id => $options{Node}, vm_id => $options{Vmid}); + # Retrieve the os info HASH from result HASH + return { PrettyName => '', Name => '', Version => '', Machine => '', Kernel => ''} + unless $vm_osinfo && ref $vm_osinfo eq 'HASH' && exists $vm_osinfo->{result}; + $vm_osinfo = $vm_osinfo->{result}; + + # Defined values depend on the guest OS + return { PrettyName => $vm_osinfo->{'pretty-name'} // $vm_osinfo->{name} // '', + Name => $vm_osinfo->{name} // '', + Version => $vm_osinfo->{'version-id'} // $vm_osinfo->{'version'} // '', + Machine => $vm_osinfo->{'machine'} // '', + Kernel => $vm_osinfo->{'kernel-release'} // $vm_osinfo->{'build-id'} // '', + } +} + +sub api_get_network_interfaces { + my ($self, %options) = @_; + + my $vm_network = $self->internal_api_get_network_interfaces(node_id => $options{Node}, vm_id => $options{Vmid}); + # Retrieve the network interfaces ARRAY from result HASH + return unless $vm_network && ref $vm_network eq 'HASH' && exists $vm_network->{'result'} && ref $vm_network->{'result'} eq 'ARRAY'; + $vm_network=$vm_network->{'result'}; + + # We returns only IPv4 addresses + # We also sort IPs to return public IPs first, then local IPs, and loopback addresses last + my (@ips_loop, @ips_local, @ips_public, @ips_interface); + my %hash_ips_by_interface; + foreach my $interface (@$vm_network) { + next unless $interface->{'ip-addresses'} && ref $interface->{'ip-addresses'} eq 'ARRAY'; + + foreach my $ip (@{$interface->{'ip-addresses'}}) { + next unless $ip->{'ip-address'}; + next if $ip->{'ip-address-type'} && $ip->{'ip-address-type'} !~ /ipv4/i; + my $name = $interface->{'name'} // ''; + + my $ip_loopback = ($ip->{'ip-address'} =~ /^127\./ || $name =~ /^lo$/i) ? 1 : 0; + + my $ip_local = $ip_loopback || centreon::plugins::misc::is_local_ip($ip->{'ip-address'}); + + $hash_ips_by_interface{$name} = { address => $ip->{'ip-address'}, local => $ip_local, loopback => $ip_loopback } + unless $hash_ips_by_interface{$name}; + + if ($ip_loopback) { + push @ips_loop, $ip->{'ip-address'}; + } elsif ($ip_local) { + push @ips_local, $ip->{'ip-address'}; + } else { + push @ips_public, $ip->{'ip-address'}; + } + } + } + + @ips_loop = sort centreon::plugins::misc::sort_ips @ips_loop; + @ips_local = sort centreon::plugins::misc::sort_ips @ips_local; + @ips_public = sort centreon::plugins::misc::sort_ips @ips_public; + + @ips_interface = map { { interface => $_, %{$hash_ips_by_interface{$_}} } } sort keys %hash_ips_by_interface; + + return [ @ips_public, @ips_local, @ips_loop ], \@ips_interface; +} + sub internal_api_get_vm_stats { my ($self, %options) = @_; @@ -501,7 +590,7 @@ Proxmox VE Rest API =head1 REST API OPTIONS -Proxmox Rest API +Proxmox VE (Virtual Environment) Rest API More Info about Proxmox VE API on https://pve.proxmox.com/wiki/Proxmox_VE_API @@ -523,7 +612,7 @@ Specify https if needed (default: 'https'). Set Proxmox VE Username API user need to have this privileges -'VM.Monitor, VM.Audit, Datastore.Audit, Sys.Audit, Sys.Syslog' +C, C, C, C, C =item B<--api-password> @@ -531,7 +620,7 @@ Set Proxmox VE Password =item B<--realm> -Set Proxmox VE Realm (pam, pve or custom) (default: 'pam'). +Set Proxmox VE Realm (C, C or C) (default: C). =item B<--timeout> diff --git a/src/apps/proxmox/ve/restapi/mode/discovery.pm b/src/apps/proxmox/ve/restapi/mode/discovery.pm index 11d0f83e0..c56e95df7 100644 --- a/src/apps/proxmox/ve/restapi/mode/discovery.pm +++ b/src/apps/proxmox/ve/restapi/mode/discovery.pm @@ -65,6 +65,36 @@ sub discovery_vm { $vm->{state} = $vms->{$vm_id}->{State}; $vm->{node_name} = $vms->{$vm_id}->{Node}; + my ($network_ips, $network_interfaces, $osinfo); + + # OSInfo and IPs are retrieved only with a QEMU VM + if ($vms->{$vm_id}->{Type} eq 'qemu') { + $osinfo = $options{custom}->api_get_osinfo( Node => $vms->{$vm_id}->{Node}, Vmid => $vms->{$vm_id}->{Vmid}); + $vm->{os_info_name} = $osinfo->{Name}; + $vm->{os_info_prettyname} = $osinfo->{PrettyName}; + $vm->{os_info_version} = $osinfo->{Version}; + $vm->{os_info_machine} = $osinfo->{Machine}; + $vm->{os_info_kernel} = $osinfo->{Kernel}; + + ($network_ips, $network_interfaces) = $options{custom}->api_get_network_interfaces( Node => $vms->{$vm_id}->{Node}, Vmid => $vms->{$vm_id}->{Vmid}); + } else { + $vm->{$_} = '' foreach (qw/os_info_name os_info_prettyname os_info_version os_info_machine os_info_kernel/); + } + + # provide the list of IP addresses if available or else provide the VM's name as only address + if ($network_ips && ref $network_ips eq 'ARRAY' && @{$network_ips} > 0) { + $vm->{ip_addresses} = $network_ips; + } else { + $vm->{ip_addresses} = [ $vms->{$vm_id}->{Name}]; + } + + # provide the list of interfaces if available or else provide the VM's name as default address + if ($network_interfaces && ref $network_interfaces eq 'ARRAY' && @{$network_interfaces} > 0) { + $vm->{iface_addresses} = $network_interfaces; + } else { + $vm->{iface_addresses} = [ { iface => 'default', ip => $vms->{$vm_id}->{Name} } ]; + } + push @$disco_data, $vm; } @@ -140,7 +170,7 @@ Resources discovery. =item B<--resource-type> -Choose the type of resources to discover (can be: 'vm', 'node'). +Choose the type of resources to discover (can be: C, C). =back diff --git a/src/centreon/plugins/backend/http/curl.pm b/src/centreon/plugins/backend/http/curl.pm index 769aa3be3..a805ba9a4 100644 --- a/src/centreon/plugins/backend/http/curl.pm +++ b/src/centreon/plugins/backend/http/curl.pm @@ -530,7 +530,7 @@ sub request { $status = 'unknown'; } - if (!$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { + if (!$options{request}->{silently_fail} && !$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { my $short_msg = $self->{response_code} . ' ' . (defined($http_code_explained->{$self->{response_code}}) ? $http_code_explained->{$self->{response_code}} : 'unknown'); diff --git a/src/centreon/plugins/backend/http/lwp.pm b/src/centreon/plugins/backend/http/lwp.pm index fb58449ce..98c867f9f 100644 --- a/src/centreon/plugins/backend/http/lwp.pm +++ b/src/centreon/plugins/backend/http/lwp.pm @@ -279,8 +279,7 @@ sub request { $self->{output}->test_eval(test => $request_options->{unknown_status}, values => { code => $self->{response_code} })) { $status = 'unknown'; } - - if (!$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { + if (!$request_options->{silently_fail} && !$self->{output}->is_status(value => $status, compare => 'ok', litteral => 1)) { my $short_msg = $self->{response}->status_line; if ($short_msg =~ /^401/) { $short_msg .= ' (' . $1 . ' authentication expected)' if (defined($self->{response}->www_authenticate) && diff --git a/src/centreon/plugins/misc.pm b/src/centreon/plugins/misc.pm index 21f57dbb4..06e327dc3 100644 --- a/src/centreon/plugins/misc.pm +++ b/src/centreon/plugins/misc.pm @@ -810,6 +810,30 @@ sub json_encode { return $encoded; } +sub is_local_ip($) { + my ($ip) = @_; + + return 0 unless $ip; + + return 1 if $ip =~ /^127\./; + return 1 if $ip =~ /^10\./; + return 1 if $ip =~ /^192\.168\./; + return 1 if $ip =~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./; + return 1 if $ip =~ /^169\.254\./; + return 1 if $ip eq '0.0.0.0'; + + return 0; +} + +# This function is used with "sort", it sorts an array of IP addresses. +# $_[0] and $_[1] correspond to Perl's special variables $a and $b used by sort. +# I can't use $a and $b directly here, otherwise Perl generates a warning: "uninitialized value". +sub sort_ips($$) { + my @a = split /\./, $_[0]; + my @b = split /\./, $_[1]; + return $a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2] || $a[3] <=> $b[3] +} + # function to assess if a string has to be excluded given an include regexp and an exclude regexp sub is_excluded { my ($string, $include_regexp, $exclude_regexp) = @_; @@ -1339,6 +1363,31 @@ Encodes an object to a JSON string. =back +=head2 is_local_ip + + my $is_local = centreon::plugins::misc::is_local_ip($ip); + +Returns 1 if an IPv4 IP is within a local address range. + +=over 4 + +=item * C<$ip> - IP to test. + +=back + +=head2 sort_ips + + my @array = ( '192.168.0.3', '127.0.0.1' ); + @array = sort centreon::plugins::misc::sort_ips @array; + +Returns a sorted array. + +=over 4 + +=item * C<@array> - An array containing IPs to be sorted. + +=back + =head2 is_excluded my $excluded = is_excluded($string, $include_regexp, $exclude_regexp); @@ -1359,8 +1408,6 @@ Returns 1 if the string is excluded, 0 if it is included. The string is excluded if $exclude_regexp is defined and matches the string, or if $include_regexp is defined and does not match the string. The string will also be excluded if it is undefined. -=cut - =head1 AUTHOR Centreon diff --git a/tests/apps/hashicorp/vault/restapi/proxmox-restapi.mockoon.json b/tests/apps/hashicorp/vault/restapi/proxmox-restapi.mockoon.json new file mode 100644 index 000000000..55f841943 --- /dev/null +++ b/tests/apps/hashicorp/vault/restapi/proxmox-restapi.mockoon.json @@ -0,0 +1,274 @@ +{ + "uuid": "7c3f15b2-59b9-4121-a4f0-fe988d60ed57", + "lastMigration": 33, + "name": "Proxmox restapi.mockoon", + "endpointPrefix": "", + "latency": 0, + "port": 3000, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "6a778c11-99ff-4a50-8004-97b6aabff215", + "type": "http", + "documentation": "Proxmox restapi ve", + "method": "get", + "endpoint": "", + "responses": [ + { + "uuid": "5c0324bb-6a0b-45f6-a01f-29fb6decc85b", + "body": "{\n \"return\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.42\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n }\n ],\n \"link-status\": \"up\"\n }\n ]\n}\n{\n \"return\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.42\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n }\n ],\n \"link-status\": \"up\"\n }\n ]\n}\n{\n \"data\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"12:54:00:32:24:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.10\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n },\n {\n \"ip-address\": \"fe80::5114:ff:ee12:1456\",\n \"prefix\": 64,\n \"ip-address-type\": \"ipv6\"\n }\n ],\n \"link-status\": \"up\",\n \"ipv4\": [\n \"192.168.1.42/24\"\n ],\n \"ipv6\": [\n \"fe80::5054:ff:fe12:3456/64\"\n ],\n \"flags\": [\"up\", \"broadcast\", \"multicast\"]\n },\n {\n \"name\": \"lo\",\n \"hardware-address\": \"00:00:00:00:00:00\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"127.0.0.1\",\n \"prefix\": 8,\n \"ip-address-type\": \"ipv4\"\n },\n {\n \"ip-address\": \"::1\",\n \"prefix\": 128,\n \"ip-address-type\": \"ipv6\"\n }\n ],\n \"link-status\": \"up\",\n \"ipv4\": [\n \"127.0.0.1/8\"\n ],\n \"ipv6\": [\n \"::1/128\"\n ],\n \"flags\": [\"up\", \"loopback\"]\n }\n ]\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "d71c2a8a-90af-40cc-8580-a7c42143e803", + "type": "http", + "documentation": "", + "method": "post", + "endpoint": "api2/json/access/ticket", + "responses": [ + { + "uuid": "440d1923-ff1f-4e23-9ac2-8ba9ca6584d1", + "body": "{\n \"data\": {\n \"username\": \"root@pam\",\n \"ticket\": \"PVE:root@pam:65b884a0::g2bBeFuM\",\n \"CSRFPreventionToken\": \"65b884a0-3d3b-4e91-bf79-b1a2c18e1bfc\",\n \"cap\": {\n \"access\": {\n \"audit\": 1,\n \"groups\": 1,\n \"permissions\": 1,\n \"realm\": 1,\n \"roles\": 1,\n \"user\": 1\n },\n \"nodes\": {\n \"Sys.Audit\": 1,\n \"Sys.Modify\": 1\n },\n \"storage\": {\n \"Datastore.Allocate\": 1,\n \"Datastore.AllocateSpace\": 1,\n \"Datastore.Audit\": 1\n },\n \"vms\": {\n \"VM.Allocate\": 1,\n \"VM.Console\": 1,\n \"VM.Migrate\": 1,\n \"VM.PowerMgmt\": 1,\n \"VM.Audit\": 1\n }\n },\n \"clustername\": \"proxmox-cluster\",\n \"hostname\": \"proxmox1.local\"\n }\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "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": "84416441-bdf8-48be-ad8c-dbfd56e99895", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/cluster/resources", + "responses": [ + { + "uuid": "3ca59867-a6c9-42c0-9365-6aee513e8338", + "body": "{\n \"data\": [\n {\n \"id\": \"node/proxmox1\",\n \"type\": \"node\",\n \"node\": \"proxmox1\",\n \"status\": \"online\",\n \"maxcpu\": 8,\n \"cpu\": 0.15,\n \"maxmem\": 34359738368,\n \"mem\": 4294967296,\n \"maxdisk\": 500000000000,\n \"disk\": 200000000000,\n \"uptime\": 86400,\n \"level\": \"\",\n \"ssl_fingerprint\": \"AA:BB:CC:...\",\n \"version\": \"8.1-2\",\n \"name\": \"rien0\"\n },\n {\n \"id\": \"qemu/101\",\n \"type\": \"qemu\",\n \"vmid\": 101,\n \"name\": \"debian-vm2\",\n \"node\": \"proxmox1\",\n \"status\": \"running\",\n \"maxcpu\": 2,\n \"cpu\": 0.20,\n \"maxmem\": 2147483648,\n \"mem\": 1073741824,\n \"maxdisk\": 34359738368,\n \"disk\": 8589934592,\n \"uptime\": 3600,\n \"template\": 0\n },\n {\n \"id\": \"storage/local\",\n \"type\": \"storage\",\n \"storage\": \"local\",\n \"node\": \"proxmox1\",\n \"status\": \"available\",\n \"enabled\": 1,\n \"content\": \"iso,vztmpl,backup\",\n \"shared\": 0,\n \"maxdisk\": 250000000000,\n \"disk\": 150000000000,\n \"name\": \"rien0\"\n }\n ]\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "type", + "value": "node", + "invert": false, + "operator": "equals" + }, + { + "target": "body", + "modifier": "", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "c0cdebe1-88ef-4fd8-8074-b4ebc77b5e9f", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/nodes/([a-z0-9]+)/([a-z0-9]+)/agent/get-osinfo", + "responses": [ + { + "uuid": "3b682249-4d8b-4c5e-91cc-272832002e77", + "body": "{\n \"data\": {\n \"id\": \"Linux\",\n \"name\": \"Debian GNU/Linux\",\n \"version-id\": \"11\",\n \"version\": \"11 (bullseye)\",\n \"pretty-name\": \"Debian GNU/Linux 11 (bullseye)\",\n \"kernel-release\": \"5.10.0-21-amd64\",\n \"kernel-version\": \"#1 SMP Debian 5.10.162-1 (2023-01-21)\",\n \"machine\": \"x86_64\"\n }\n}", + "latency": 0, + "statusCode": 500, + "label": "", + "headers": [ + { + "key": "access-control-allow-headers", + "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With" + }, + { + "key": "access-control-allow-methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "access-control-allow-origin", + "value": "*" + }, + { + "key": "content-security-policy", + "value": "default-src 'none'" + }, + { + "key": "content-type", + "value": "text/html; charset=utf-8" + }, + { + "key": "x-content-type-options", + "value": "nosniff" + } + ], + "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": "78d64704-80ab-4420-aa6b-2c807930f0e6", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/nodes/([a-z0-9]+)/([a-z0-9]+)/agent/network-get-interfaces", + "responses": [ + { + "uuid": "62d7f560-f89c-41ca-8a36-46da872671b0", + "body": "{\n \"data\": [\n {\n \"name\": \"lo\",\n \"hardware-address\": null,\n \"ip-addresses\": [\n {\n \"ip-address-type\": \"ipv4\",\n \"ip-address\": \"127.0.0.1\",\n \"prefix\": 8\n }\n ],\n \"mtu\": 65536,\n \"state\": \"UNKNOWN\",\n \"type\": \"loopback\",\n \"flags\": [\"up\", \"loopback\", \"multicast\"]\n },\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address-type\": \"ipv4\",\n \"ip-address\": \"192.168.1.101\",\n \"prefix\": 24\n },\n {\n \"ip-address-type\": \"ipv6\",\n \"ip-address\": \"fe80::5054:ff:fe12:3456\",\n \"prefix\": 64\n }\n ],\n \"mtu\": 1500,\n \"state\": \"UP\",\n \"type\": \"ethernet\",\n \"flags\": [\"up\", \"broadcast\", \"multicast\"]\n },\n {\n \"name\": \"eth1\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address-type\": \"ipv4\",\n \"ip-address\": \"212.168.1.101\",\n \"prefix\": 24\n },\n {\n \"ip-address-type\": \"ipv6\",\n \"ip-address\": \"fe80::5054:ff:fe12:3456\",\n \"prefix\": 64\n }\n ],\n \"mtu\": 1500,\n \"state\": \"UP\",\n \"type\": \"ethernet\",\n \"flags\": [\"up\", \"broadcast\", \"multicast\"]\n }\n ]\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "6a778c11-99ff-4a50-8004-97b6aabff215" + }, + { + "type": "route", + "uuid": "d71c2a8a-90af-40cc-8580-a7c42143e803" + }, + { + "type": "route", + "uuid": "84416441-bdf8-48be-ad8c-dbfd56e99895" + }, + { + "type": "route", + "uuid": "c0cdebe1-88ef-4fd8-8074-b4ebc77b5e9f" + }, + { + "type": "route", + "uuid": "78d64704-80ab-4420-aa6b-2c807930f0e6" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [], + "callbacks": [] +} \ No newline at end of file diff --git a/tests/apps/proxmox/ve/restapi/discovery.robot b/tests/apps/proxmox/ve/restapi/discovery.robot new file mode 100644 index 000000000..fb86089cf --- /dev/null +++ b/tests/apps/proxmox/ve/restapi/discovery.robot @@ -0,0 +1,36 @@ +*** Settings *** +Documentation Proxmox VE REST API Mode Discovery + +Resource ${CURDIR}${/}..${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}proxmox.mockoon.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=apps::proxmox::ve::restapi::plugin +... --mode discovery +... --hostname=${HOSTNAME} +... --api-username=xx +... --api-password=xx +... --proto=http +... --port=${APIPORT} + +*** Test Cases *** +Discovery ${tc} + [Tags] storage api hpe hp + ${command} Catenate + ... ${CMD} + ... ${extra_options} + + Ctn Run Command And Check Result As Regexp ${command} ${expected_regexp} + + Examples: tc extraoptions expected_regexp -- + ... 1 ${EMPTY} "discovered_items":3 + ... 2 --resource-type=vm (?=.*"ip_addresses":\\\\["123.321.123.321","127.0.0.1"\\\\],)(?=.*"os_info_name":"XxXxXx GNU/Linux") + ... 3 --resource-type=node ^(?!.*(ip_addresses|os_info_name)).*$ diff --git a/tests/apps/proxmox/ve/restapi/proxmox.mockoon.json b/tests/apps/proxmox/ve/restapi/proxmox.mockoon.json new file mode 100644 index 000000000..08c14c7af --- /dev/null +++ b/tests/apps/proxmox/ve/restapi/proxmox.mockoon.json @@ -0,0 +1,293 @@ +{ + "uuid": "7c3f15b2-59b9-4121-a4f0-fe988d60ed57", + "lastMigration": 33, + "name": "Proxmox restapi.mockoon", + "endpointPrefix": "", + "latency": 0, + "port": 3000, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "6a778c11-99ff-4a50-8004-97b6aabff215", + "type": "http", + "documentation": "Proxmox restapi ve", + "method": "get", + "endpoint": "", + "responses": [ + { + "uuid": "5c0324bb-6a0b-45f6-a01f-29fb6decc85b", + "body": "{\n \"return\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.42\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n }\n ],\n \"link-status\": \"up\"\n }\n ]\n}\n{\n \"return\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"52:54:00:12:34:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.42\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n }\n ],\n \"link-status\": \"up\"\n }\n ]\n}\n{\n \"data\": [\n {\n \"name\": \"eth0\",\n \"hardware-address\": \"12:54:00:32:24:56\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"192.168.1.10\",\n \"prefix\": 24,\n \"ip-address-type\": \"ipv4\"\n },\n {\n \"ip-address\": \"fe80::5114:ff:ee12:1456\",\n \"prefix\": 64,\n \"ip-address-type\": \"ipv6\"\n }\n ],\n \"link-status\": \"up\",\n \"ipv4\": [\n \"192.168.1.42/24\"\n ],\n \"ipv6\": [\n \"fe80::5054:ff:fe12:3456/64\"\n ],\n \"flags\": [\"up\", \"broadcast\", \"multicast\"]\n },\n {\n \"name\": \"lo\",\n \"hardware-address\": \"00:00:00:00:00:00\",\n \"ip-addresses\": [\n {\n \"ip-address\": \"127.0.0.1\",\n \"prefix\": 8,\n \"ip-address-type\": \"ipv4\"\n },\n {\n \"ip-address\": \"::1\",\n \"prefix\": 128,\n \"ip-address-type\": \"ipv6\"\n }\n ],\n \"link-status\": \"up\",\n \"ipv4\": [\n \"127.0.0.1/8\"\n ],\n \"ipv6\": [\n \"::1/128\"\n ],\n \"flags\": [\"up\", \"loopback\"]\n }\n ]\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "d71c2a8a-90af-40cc-8580-a7c42143e803", + "type": "http", + "documentation": "", + "method": "post", + "endpoint": "api2/json/access/ticket", + "responses": [ + { + "uuid": "440d1923-ff1f-4e23-9ac2-8ba9ca6584d1", + "body": "{\n \"data\": {\n \"username\": \"root@pam\",\n \"ticket\": \"PVE:root@pam:ccccccc::g2bBfFuM\",\n \"CSRFPreventionToken\": \"aaaaaaa-3d3b-4e91-bf79-b1a2c18eabfc\",\n \"cap\": {\n \"access\": {\n \"audit\": 1,\n \"groups\": 1,\n \"permissions\": 1,\n \"realm\": 1,\n \"roles\": 1,\n \"user\": 1\n },\n \"nodes\": {\n \"Sys.Audit\": 1,\n \"Sys.Modify\": 1\n },\n \"storage\": {\n \"Datastore.Allocate\": 1,\n \"Datastore.AllocateSpace\": 1,\n \"Datastore.Audit\": 1\n },\n \"vms\": {\n \"VM.Allocate\": 1,\n \"VM.Console\": 1,\n \"VM.Migrate\": 1,\n \"VM.PowerMgmt\": 1,\n \"VM.Audit\": 1\n }\n },\n \"clustername\": \"WwWwW-cluster\",\n \"hostname\": \"WwWwW1.local\"\n }\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "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": "84416441-bdf8-48be-ad8c-dbfd56e99895", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/cluster/resources", + "responses": [ + { + "uuid": "3ca59867-a6c9-42c0-9365-6aee513e8338", + "body": "{\n \"data\": [\n {\n \"id\": \"node/www1\",\n \"type\": \"node\",\n \"node\": \"www1\",\n \"status\": \"online\",\n \"maxcpu\": 8,\n \"cpu\": 0.15,\n \"maxmem\": 34359738368,\n \"mem\": 4294967296,\n \"maxdisk\": 500000000000,\n \"disk\": 200000000000,\n \"uptime\": 86400,\n \"level\": \"\",\n \"ssl_fingerprint\": \"AA:BB:CC:...\",\n \"version\": \"1.1-2\",\n \"name\": \"rien0\"\n },\n {\n \"id\": \"qemu/101\",\n \"type\": \"qemu\",\n \"vmid\": 101,\n \"name\": \"cccccc-vm2\",\n \"node\": \"dddddd1\",\n \"status\": \"running\",\n \"maxcpu\": 2,\n \"cpu\": 0.20,\n \"maxmem\": 2147483648,\n \"mem\": 1073741824,\n \"maxdisk\": 34359738368,\n \"disk\": 8589934592,\n \"uptime\": 3600,\n \"template\": 0\n },\n {\n \"id\": \"storage/local\",\n \"type\": \"storage\",\n \"storage\": \"local\",\n \"node\": \"cccccc1\",\n \"status\": \"available\",\n \"enabled\": 1,\n \"content\": \"iso,vztmpl,backup\",\n \"shared\": 0,\n \"maxdisk\": 250000000000,\n \"disk\": 150000000000,\n \"name\": \"rien0\"\n }\n ]\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "query", + "modifier": "type", + "value": "node", + "invert": false, + "operator": "equals" + }, + { + "target": "body", + "modifier": "", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "c0cdebe1-88ef-4fd8-8074-b4ebc77b5e9f", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/nodes/([a-z0-9]+)/qemu/([a-z0-9]+)/agent/get-osinfo", + "responses": [ + { + "uuid": "3b682249-4d8b-4c5e-91cc-272832002e77", + "body": "{\"data\":{\"result\":{\"machine\":\"x86_64\",\"version\":\"111 (XxXxXx)\",\"version-id\":\"111\",\"pretty-name\":\"XXXXX GNU/Linux 111 (XxXxXx)\",\"id\":\"xXxXx\",\"name\":\"XxXxXx GNU/Linux\",\"kernel-version\":\"#1 SMP PREEMPT_DYNAMIC XXXXX 6.0.0-1 (2025-01-01)\",\"kernel-release\":\"6.0.0-32-amd64\"}}}", + "latency": 0, + "statusCode": 500, + "label": "", + "headers": [ + { + "key": "access-control-allow-headers", + "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With" + }, + { + "key": "access-control-allow-methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "access-control-allow-origin", + "value": "*" + }, + { + "key": "content-security-policy", + "value": "default-src 'none'" + }, + { + "key": "content-type", + "value": "text/html; charset=utf-8" + }, + { + "key": "x-content-type-options", + "value": "nosniff" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "34ffe7cc-4968-43e7-b8c4-b57c248f20ef", + "body": "{\"message\":\"QEMU guest agent is not running\\n\",\"data\":null}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "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": "78d64704-80ab-4420-aa6b-2c807930f0e6", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "api2/json/nodes/([a-z0-9]+)/qemu/([a-z0-9]+)/agent/network-get-interfaces", + "responses": [ + { + "uuid": "62d7f560-f89c-41ca-8a36-46da872671b0", + "body": "{\"data\":{\"result\":[{\"ip-addresses\":[{\"ip-address\":\"127.0.0.1\",\"prefix\":8,\"ip-address-type\":\"ipv4\"},{\"prefix\":128,\"ip-address-type\":\"ipv6\",\"ip-address\":\"::1\"}],\"hardware-address\":\"00:00:00:00:00:00\",\"name\":\"lo\",\"statistics\":{\"tx-packets\":332,\"rx-errs\":0,\"tx-errs\":0,\"rx-packets\":332,\"rx-dropped\":0,\"tx-bytes\":56962,\"rx-bytes\":56962,\"tx-dropped\":0}},{\"statistics\":{\"tx-errs\":0,\"rx-packets\":682355687,\"tx-packets\":492437157,\"rx-errs\":0,\"rx-bytes\":172127793,\"tx-dropped\":0,\"rx-dropped\":0,\"tx-bytes\":917205385},\"name\":\"eth0\",\"hardware-address\":\"aa:bb:cc:dd:ee:ff\",\"ip-addresses\":[{\"ip-address-type\":\"ipv4\",\"prefix\":24,\"ip-address\":\"123.321.123.321\"},{\"ip-address\":\"aaaa::bbbb:cccc:dddd:eeee\",\"ip-address-type\":\"ipv6\",\"prefix\":64}]}]}}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "6a778c11-99ff-4a50-8004-97b6aabff215" + }, + { + "type": "route", + "uuid": "d71c2a8a-90af-40cc-8580-a7c42143e803" + }, + { + "type": "route", + "uuid": "84416441-bdf8-48be-ad8c-dbfd56e99895" + }, + { + "type": "route", + "uuid": "c0cdebe1-88ef-4fd8-8074-b4ebc77b5e9f" + }, + { + "type": "route", + "uuid": "78d64704-80ab-4420-aa6b-2c807930f0e6" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [], + "callbacks": [] +} \ No newline at end of file diff --git a/tests/resources/spellcheck/stopwords.txt b/tests/resources/spellcheck/stopwords.txt index 4ac0d00a8..83f0056d8 100644 --- a/tests/resources/spellcheck/stopwords.txt +++ b/tests/resources/spellcheck/stopwords.txt @@ -58,6 +58,7 @@ cpu-utilization-5s CX Cyberoam Datacore +Datastore datasource datastore datastores @@ -128,6 +129,7 @@ in-ucast io-cards iops IpAddr +IPs ip-label ipsec ipv4 @@ -238,6 +240,7 @@ PPID prct Primera Procurve +Proxmox proto --proxyurl psu @@ -298,6 +301,7 @@ total-online-prct total-oper-down total-oper-up tower-cli +TLSv1 TrendMicro tsdb-version UCD @@ -323,6 +327,7 @@ vdomain VDSL2 Veeam VeloCloud +VE vhost VM VMs