#!/usr/bin/perl -w use strict; use warnings; use JSON; use Getopt::Long; use File::Which; use File::Path qw(make_path); use File::Basename; use Data::Dumper; my $pvesh = which('pvesh'); my $json = {}; my $pretty = 0; my ($cluster,$guest,$node,$storage,$pool) = undef; # Max age of cached values my $cache = 60; my $cache_dir = '/tmp/zbx_pve_cache/'; GetOptions( 'cluster' => \$cluster, 'guest=i' => \$guest, 'node=s' => \$node, 'storage=s' => \$storage, 'pool=s' => \$pool, 'pretty' => \$pretty, 'cache=i' => \$cache, 'cache-dir=s' => \$cache_dir ); # Before Buster / PVE6, pvesh don't support (or need) --output-format=json # So try to detect previous version and adapt opt my $pvesh_opt = '--output-format=json'; if (-f '/etc/os-release'){ open my $fh, '<', '/etc/os-release'; foreach my $line (<$fh>){ my ($var, $val) = split(/=/, $line); $val =~ s/"(.*)"/$1/; if ($var eq 'VERSION_ID' and int $val < 10){ $pvesh_opt = ''; last; } } } if ($cluster){ my $cluster = get_api_data('/cluster/status'); # Set default values so monitoring works for single node, without cluster setup $json->{status} = { all_online => 1, quorate => 1, nodes => 1, name => 'default', version => 1 }; # Set default global stats $json->{network} = { in => 0, out => 0 }; $json->{disk} = { read => 0, write => 0 }; my @nodes = (); foreach my $item (@{$cluster}){ if ($item->{type} eq 'cluster'){ $json->{status}->{$_} = $item->{$_} foreach (qw(quorate nodes name version)); } elsif ($item->{type} eq 'node' and $item->{online}){ push @nodes, $item->{name}; } elsif ($item->{type} eq 'node'){ $json->{status}->{all_online} = 0; } } foreach my $node (@nodes){ my $n = get_api_data("/nodes/$node/status"); # Here we gather (and sum) some info about individual nodes to get the total number of # CPU, the amount of memory etc... $json->{memory}->{$_} += $n->{memory}->{$_} foreach (qw(free total used)); $json->{ksm}->{$_} += $n->{ksm}->{$_} foreach (qw(shared)); $json->{cpuinfo}->{$_} += $n->{cpuinfo}->{$_} foreach (qw(cpus sockets)); $json->{loadavg}[$_] += $n->{loadavg}[$_] foreach (0..2); } # We want average load avg of the cluster, not the sum of individual loads $json->{loadavg}[$_] = sprintf "%.2f", $json->{loadavg}[$_] / $json->{status}->{nodes} foreach (0..2); my $guests = get_api_data('/cluster/resources', '--type=vm'); foreach my $guest (@{$guests}){ $json->{network}->{in} += $guest->{netin} || 0; $json->{network}->{out} += $guest->{netout} || 0; $json->{disk}->{read} += $guest->{diskread} || 0; $json->{disk}->{write} += $guest->{diskwrite} || 0; } } elsif ($node){ foreach my $item (qw(status version subscription)){ $json->{$item} = get_api_data("/nodes/$node/$item"); } } elsif ($guest){ my $guests = get_api_data('/cluster/resources', '--type=vm'); foreach my $g (@{$guests}){ if ($g->{vmid} eq $guest){ $json = $g; last; } } } elsif ($pool){ my $pool = get_api_data("/pools/$pool"); $json->{comment} = $pool->{comment}; foreach my $type (qw(qemu lxc)){ $json->{$_}->{$type} = 0 foreach (qw(guests templates)); } foreach my $item (@{$pool->{members}}){ if ($item->{type} =~ m/^(qemu|lxc)$/ and !$item->{template}){ $json->{guests}->{$_} += $item->{$_} foreach (qw(maxcpu diskread diskwrite maxdisk mem maxmem netin netout)); $json->{guests}->{used_cpu} += $item->{cpu} * $item->{maxcpu}; $json->{guests}->{$item->{type}}++; } if ($item->{type} =~ m/^(qemu|lxc)$/ and $item->{template}){ $json->{templates}->{$_} += $item->{$_} foreach (qw(maxdisk)); $json->{templates}->{$item->{type}}++; } } $json->{guests}->{$_} //= 0 foreach (qw(cpu maxcpu diskread diskwrite maxdisk mem maxmem netin netout)); $json->{templates}->{$_} //= 0 foreach (qw(maxdisk)); $json->{guests}->{cpu} = ($json->{guests}->{maxcpu} == 0) ? 0 : $json->{guests}->{used_cpu} / $json->{guests}->{maxcpu}; } elsif ($storage){ my $stores = get_api_data('/cluster/resources', '--type=storage'); foreach my $s (@{$stores}){ if ($s->{storage} eq $storage){ $json->{maxdisk} = $s->{maxdisk}; $json->{disk} = $s->{disk}; last; } } } else{ print 'ZBX_NOTSUPPORTED'; exit 0; } print to_json($json, { pretty => $pretty }) . "\n"; # Helper which will either get data from # the cache if its fresh enough, or query the API # and save the result in the cache for later sub get_api_data { my ($path, $query_opt) = @_; $query_opt ||= ''; my $opt_filename = $query_opt; $opt_filename =~ s/[\-=]/_/g; my $res; # Is the cache existing and fresh enough ? if (-f $cache_dir . $path . $opt_filename and int((-M $cache_dir . $path . $opt_filename)*60*60*24) < $cache){ { local $/; # Enable slurp open my $fh, "<", $cache_dir . $path . $opt_filename; $res = <$fh>; close $fh; } } else { $res = qx($pvesh get $path $query_opt $pvesh_opt 2>/dev/null); # Save the result in the cache for later retrival eval{ my $dir = (fileparse($path))[1]; make_path($cache_dir . $dir, { chmod => 700 }); }; open my $fh, ">", $cache_dir . $path . $opt_filename; print $fh $res; close $fh; } return from_json($res); }