#!/usr/bin/perl
#
# AUTHORS:
#	Copyright (C) 2008 Altinity Limited
#	Written by Neil Ferguson, kindly sponsored by GotVMail
#
#    This file is part of Opsview
#
#    Opsview is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    Opsview is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Opsview; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

use strict;
use lib qw ( /usr/local/nagios/perl/lib );
use Storable qw(lock_store lock_retrieve);
use Net::SNMP;
use Getopt::Std;

# About us
my $script         = "check_snmp_vmware_netio";
my $script_version = "0.1";
my $script_description =
  "Reports the network throughput on a specified VMware interface";
my $store_fn = "/tmp/$script.dat";
my $store;

my $hostname = '';

# SNMP variables
my $oid_sysDescr =
  ".1.3.6.1.2.1.1.1.0";    # Used to check whether SNMP is actually responding
my $oid_namebase      = ".1.3.6.1.4.1.6876.2.1.1.2.";
my $oid_vmidbase      = ".1.3.6.1.4.1.6876.2.1.1.7.";
my $oid_nicbase       = ".1.3.6.1.4.1.6876.3.4.1.3.";
my $oid_macbase       = ".1.3.6.1.4.1.6876.3.4.1.4.";
my $oid_rxbase        = ".1.3.6.1.4.1.6876.3.4.1.9.";
my $oid_txbase        = ".1.3.6.1.4.1.6876.3.4.1.7.";
my $vm_name           = "";
my $vm_mac            = "";
my $mode              = "name";
my $community         = "public";                       # Default community
my $timeout           = 10;                             # SNMP timeout
my $retval            = 3;
my $retmsg            = 3;
my $perfdata          = "";
my $warning           = 0;
my $critical          = 0;
my $warn_in           = 0;
my $crit_in           = 0;
my $warn_out          = 0;
my $crit_out          = 0;
my $throughput_in     = 0;
my $throughput_in_kb  = 0;
my $throughput_out    = 0;
my $throughput_out_kb = 0;
my $version           = "2c";

our ( $s, $e );

# Command line arguments
our (
  $opt_h, $opt_H, $opt_C, $opt_t, $opt_v,
  $opt_m, $opt_w, $opt_c, $opt_i, $opt_o
);
getopts("hH:C:t:v:m:w:c:io");
if ($opt_h) {
  usage();
  exit 0;
}
if ($opt_H) {
  $hostname = $opt_H;
}
else {
  print "No hostname specified\n";
  usage();
  exit 3;
}
if ($opt_C) {
  $community = $opt_C;
}
if ($opt_t) {

  # Validity test - must be numeric
  unless ( $opt_t =~ /^\d+$/ ) {
    print "Specify time in seconds - $opt_t is not a valid integer\n";
    exit 3;
  }
  $timeout = $opt_t;
}
if ($opt_v) {
  $vm_name = $opt_v;
}
elsif ($opt_m) {
  if ($opt_v) {
    print "Must specify either VM name or MAC address\n";
    usage();
    exit 3;
  }
  $vm_mac = $opt_m;
  $mode   = "mac";
}
else {
  print "Must specify either MAC address or VM name\n";
  usage();
  exit 3;
}
if ($opt_w) {
  if ( $opt_w =~ /^\d+$/ ) {
    $warning = $opt_w;
  }
  else {
    print "Must specify warning threshold as an integer in KB/sec\n";
    usage();
    exit 3;
  }
}
if ($opt_c) {
  if ( $opt_c =~ /^\d+$/ ) {
    $critical = $opt_c;
  }
  else {
    print "Must specify critical threshold as an integer in KB/sec\n";
    usage();
    exit 3;
  }
}
if ( $opt_i && $opt_o ) {
  print
    "Specify either of -i or -o, or specify neither option to mean 'both'\n";
  usage();
  exit 3;
}
if ($opt_i) {
  $warn_in = $warning;
  $crit_in = $critical;
}
elsif ($opt_o) {
  $warn_out = $warning;
  $crit_out = $critical;
}
else {
  $warn_in  = $warning;
  $crit_in  = $critical;
  $warn_out = $warning;
  $crit_out = $critical;
}

sub usage {
  print <<EOF
--------------------------------------------------------------------
$script $script_version

$script_description

Usage: $script -H <hostname> -C <community> [-t <timeout>] ...

Options: -H     Hostname or IP address
         -C     SNMP community string
         -t     SNMP timeout (in seconds)
         -v     VM name (alternatively, specify MAC)
         -m     MAC address
         -w     Warning threshold (KB/sec)
         -c     Critical threshold (KB/sec)

Note: Using the VM name is fine when you only have one interface
on that VM, but you should use the MAC address otherwise.

--------------------------------------------------------------------     
Copyright 2008 Altinity Limited  

Plugin development was sponsored by GotVMail (http://www.gotvmail.com)
         
This program is free software; you can redistribute it or modify
it under the terms of the GNU General Public License
------------------------------------------------------------------
EOF

}

# Call this when you know you'll get a single value back
sub get_oid_value {
  our ( $oid, $result, $status, $returnstring );

  $oid = shift(@_);

  if ( !defined( $s->get_request($oid) ) ) {
    if ( !defined( $s->get_request($oid_sysDescr) ) ) {
      print "SNMP agent not responding\n";
      exit 3;
    }
    else {
      print "SNMP OID does not exist\n";
      exit 3;
    }
  }

  foreach ( $s->var_bind_names() ) {
    $result = $s->var_bind_list()->{$_};
  }

  return $result;
}

sub sane_units {

 # Input should be in kilobytes - we'll convert to something more human readible
  my $kbits    = shift(@_);
  my $gigabits = 1024 * 1024;
  my $megabits = 1024;

  # Gigabits
  if ( $kbits > $gigabits ) {
    return int( ( 100 * $kbits ) / $gigabits ) / 100 . " GB/s";
  }
  elsif ( $kbits > $megabits - 1 ) {
    return int( ( 100 * $kbits ) / $megabits ) / 100 . " MB/s";
  }
  else {
    return $kbits . " KB/s";
  }
}

sub get_vm_id {
  my $name = shift;
  my $i    = 0;
  my $result;

  # Avoid accidental infinite loops by limiting
  # ourselves to 100 VMs.  Real solution is to
  # get the whole table.
  while ( $i < 100 ) {
    $result = get_oid_value( $oid_namebase . $i );

    if ( $result eq "" || $result eq "noSuchInstance" ) {
      last;
    }
    elsif ( $result =~ /^$name$/i ) {
      return get_oid_value( $oid_vmidbase . $i );
    }
    $i++;
  }

  return undef;
}

sub get_nic_id {
  my $oid_base = shift(@_);
  my $search   = shift(@_);
  my $result;
  my $i = 1;

  while ( $i < 100 ) {
    $result = get_oid_value( $oid_base . $i );

    if ( $result eq "" || $result eq "noSuchInstance" ) {
      last;
    }
    elsif ( $result =~ /^$search$/i ) {
      return $i;
    }
    $i++;
  }
}

sub get_nic_data {
  my $mode      = shift(@_);
  my $vm        = shift(@_);
  my $mac       = shift(@_);
  my $storename = $hostname . $vm;
  my $txrate    = 0;
  my $rxrate    = 0;
  our ( $vmid, $nic, $rxkb, $txkb );

  if ( $mode eq "name" ) {

    # Get VM ID
    $vmid = get_vm_id($vm);
    return undef unless defined($vmid);

    # Get NIC ID from VM ID
    $nic = get_nic_id( $oid_nicbase, $vmid );
  }
  else {

    # Store by MAC not by name
    $storename = $hostname . $mac;

    # Get NIC ID from MAC ID
    $nic = get_nic_id( $oid_macbase, $mac );
  }

  # Ensure we have a NIC id
  return undef unless defined($nic);

  # Get throughputs
  $rxkb = get_oid_value( $oid_rxbase . $nic );
  $txkb = get_oid_value( $oid_txbase . $nic );

  # Retrieve info from store if it exists
  if ( -f $store_fn ) {
    $store = lock_retrieve($store_fn);
  }

  if ( defined( $store->{$storename} ) ) {
    my $time = $store->{$storename}[0];
    $rxrate = ( $rxkb - $store->{$storename}[1] ) / $time;
    $txrate = ( $txkb - $store->{$storename}[2] ) / $time;

    if ( $rxrate < 0 ) { $rxrate = 0; }
    if ( $txrate < 0 ) { $txrate = 0; }
  }

  # Always write current data
  $store->{$storename} = [ time(), $rxkb, $txkb ];
  lock_store( $store, $store_fn );

  # Calculate rates
  return ( int($rxrate), int($txrate) );
}

# Create the SNMP session
$version = "2c";
( $s, $e ) = Net::SNMP->session(
  -community => $community,
  -hostname  => $hostname,
  -version   => $version,
  -timeout   => $timeout,
);

if ( !defined( $s->get_request($oid_sysDescr) ) ) {

  # If we can't connect using SNMPv1 lets try as SNMPv2
  $s->close();
  sleep 0.5;
  $version = "1";
  ( $s, $e ) = Net::SNMP->session(
    -community => $community,
    -hostname  => $hostname,
    -version   => $version,
    -timeout   => $timeout,
  );
  if ( !defined( $s->get_request($oid_sysDescr) ) ) {
    print "Agent not responding, tried SNMP v1 and v2\n";
    exit 3;
  }
}

# Check for an SNMP error first...
if ( $s->error ) {
  print "UNKNOWN - " . $s->error . "|\n";
  exit 3;
}

# Check what the value is
( $throughput_in_kb, $throughput_out_kb ) =
  get_nic_data( $mode, $vm_name, $vm_mac );

# Check we got some data
if ( !defined($throughput_in_kb) ) {
  print "UNKNOWN - Could not find data for the specified interface\n";
  exit 3;
}

$throughput_in  = sane_units($throughput_in_kb);
$throughput_out = sane_units($throughput_out_kb);

$retmsg =
" - $throughput_in in, $throughput_out out | throughput_in=$throughput_in_kb;$warn_in;$crit_in;; throughput_out=$throughput_out_kb;$warn_out;$crit_out;;\n";

if ( ( $throughput_in_kb > $crit_in && $crit_in > 0 )
  || ( $throughput_out_kb > $crit_out && $crit_out > 0 ) )
{
  print "CRITICAL $retmsg";
  exit 2;
}
elsif ( ( $throughput_in_kb > $warn_in && $warn_in > 0 )
  || ( $throughput_out_kb > $warn_out && $warn_out > 0 ) )
{
  print "WARNING $retmsg";
  exit 1;
}
else {
  print "OK $retmsg";
  exit 0;
}
