Первая часть была тут: Nagios — система мониторинга и некоторые самодельные плагины. Как и обещал, часть вторая, интереснее.

Здесь я расскажу, как и что можно в nagios мониторить в vmware, CMC-TC, Synology, ИБП (APC и Chloride), принтерах и о мониторинге интерфейсов в Cisco по именам и зачем это нужно.

Nagios и VMWARE


У нас стоят кластеры vmware из vSphere 5.1, лицензионные, с техподдержкой. Хотя техподдержка vmware — это отдельно и не к ночи. По snmp там можно мониторить стандартную ветку HOST-RESOURCES-V2-MIB (oid 1.3.6.1.2.1.25), там как обычно много интересного, память, процессора, сеть. Насчет сети есть оговорка — версии vSphere (подозреваю, что и iESX) до какого-то билда имеют косяк, который состоит в том, что 64-битные счетчики трафика работают для исходящего трафика и возвращают 0 для входящего. Потом vmware это пофиксила, но если у вас вдруг при мониторинге сетевого трафика исходящий есть, а входящего нет — это оно самое, не пугайтесь, надо поднимать версию.

Системы хранения (а как легко догадаться, у кластера они внешние и могут отпадать) тоже доступны в 25 ветке (oid 1.3.6.1.2.1.25.2) за одной оговоркой — vSphere нигде и никак не возвращает имена подключенных дисков. То есть в hrDeviceDescr видим название и номер lun (LUN HP HSV300 0953 naa.50014380025cf510), а в точке монтирования что-то типа /vmfs/volumes/4e343177-a470f8bb-4e25-04257f664f9e, partiton label тоже крайне информативен (naa.600508b1001c1bc8b9036a0d8b117c88:1).

А админам даже vmware важно видеть имя диска, а не его шифр. Поэтому пришлось колхозить свой скрипт, который бы мониторил диски на vmware и описывал их в терминах, понятным окружающим.

check_vmwarediskstatus.pl
#!/usr/local/bin/perl
#
# (C) Smithson Inc
#
#

#use strict;
use lib "/usr/local/libexec/nagios";
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use vars qw($PROGNAME);
use Getopt::Long;
use  Time::gmtime;
use vars qw($opt_V $opt_h $verbose $opt_w $opt_c $opt_H);

$PROGNAME = `basename $0`;

my $warning = 90;
my $critical = 95;
my $community = 'public';
my $MAX = 16;

Getopt::Long::Configure('bundling');
GetOptions
        (
         "w=s" => \$opt_w, "warning=s"  => \$opt_w,
         "c=s" => \$opt_c, "critical=s" => \$opt_c,
         "H=s" => \$opt_H, "hostname=s" => \$opt_H);


$opt_H = shift unless ($opt_H);
my $host = $1 if ($opt_H =~ m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z][-a-zA-Z0]+(\.[a-zA-Z][-a-zA-Z0]+)*)$/);
if (!(defined($host))) { print_usage();  exit $ERRORS{'UNKNOWN'}; };

my $server = $host;

($opt_c) || ($opt_c = shift) || ($opt_c = 95);
$critical = $1 if ($opt_c =~ /([0-9]+)/);

($opt_w) || ($opt_w = shift) || ($opt_w = 90);
$warning = $1 if ($opt_w =~ /([0-9]+)/);

$RETURN_CODE = $ERRORS{'UNKNOWN'};

$SNMP = "/usr/local/bin/snmpwalk -v2c -c $community";

$OID_SYSNAME = '1.3.6.1.2.1.1.5.0';
$OID_STORAGE = '1.3.6.1.2.1.25.2';

my $s = getStorageState($server);
my $code = 'UNKNOWN';

if ($RETURN_CODE == $ERRORS{'OK'}) { $code = 'OK '; }
if ($RETURN_CODE == $ERRORS{'WARNING'}) { $code = 'WARNING '; }
if ($RETURN_CODE == $ERRORS{'CRITICAL'}) { $code = 'CRITICAL '; }

print "$code - $s \n";

exit $RETURN_CODE;
#  ================================================================
sub getStorageState {
  my $ip = shift;
  my $ret = '';
  my $i;
  my @size, @used, @name= ();

  for ($i = 0; $i<$MAX+1; $i++) { $size[$i] = 0; }

  my @s = getSNMPdata($ip, $OID_STORAGE);

  if ($#s == 0) { return ''; } 

  $RETURN_CODE = $ERRORS{'OK'};
  foreach $q (@s) {
     chomp($q);
     if ($q =~ /hrStorageSize\.(\d+) = INTEGER: (\d+)/) {
	$size[$1] = $2;
     }
     if ($q =~ /hrStorageUsed\.(\d+) = INTEGER: (\d+)/) {
	$used[$1] = $2;
     }
     if ($q =~ /hrStorageDescr\.(\d+) = STRING: \/vmfs\/volumes\/(.+)/) {
	$name[$1] = getdatastoragename($2);
     }
     if ($q =~ /hrStorageAllocationFailures\.(\d+) = Counter32: (\d+)/) {
	if ($2 != 0) {    
	    $ret = $ret."\# $1 failure! (code $2) ";
	    $RETURN_CODE = $ERRORS{'CRITICAL'};
        }
     }
  };


  for ($i = 0; $i<$MAX+1; $i++) { 
    if ($size[$i] > 0) {
      my $p = (100*$used[$i])/$size[$i];
      my $sp = sprintf("%0.2f", $p);
      if (length($ret) > 1) { $ret = $ret.", "; }
      $ret = $ret."$name[$i]=$sp\%";
      if (($p > $warning) && ($RETURN_CODE == $ERRORS{'OK'})) { $RETURN_CODE = $ERRORS{'WARNING'}; }
      if ($p > $critical) { $RETURN_CODE = $ERRORS{'CRITICAL'}; }
    };
  }

  return $ret;
}
#  ================================================================
sub getSNMPdata {
  my $ip  = shift;
  my $snmpquery = shift;
  my $q, @dat;

  $q = "$SNMP $ip $snmpquery";
  @dat = `$q`;
  return @dat;
}
#  ================================================================
sub getSNMPstring {
  my $ip  = shift;
  my $snmpquery = shift;
  my $q, $dat;

  $q = "$SNMP $ip $snmpquery";
  $dat = `$q`;
  chomp $dat;
  if (length($dat) < 1) {
    return '';
  }
  if ($dat =~ /= STRING:\ (.+)/) { $dat = $1 };
  return $dat;
}
#  ================================================================
sub getdatastoragename () {
    my $id  = shift;
    my $ret = '?';
    my %names = (
		 '4b4468fe-c310d5c8-e0ee-002481e8ae94' => 'EVA2Tb',
		 '4f3b9661-776069e9-5002-78e7d158f891' => 'EVA360Gb',
		 '54f5b0bc-97f70749-e088-f0921c1099b0' => 'NN1.5Tb',
		 '52e8d119-309fe1b1-10fb-d89d676e0ce0' => 'EVA2TB_2',
		 '55c07bc8-6365350e-c56b-3c4a92e5f7f4' => '0CLONE',
		);
    foreach $key (keys %names) {
	if ($id eq $key) { return $names{$key}; };
    };

    return $ret;
    
}
#  ================================================================
sub print_usage () {
        print "Usage: $PROGNAME -H <host> [-w <warn>] [-c <crit>] \n";
}
#  ================================================================
sub print_help () {
        print_revision($PROGNAME,'');
        print "Copyright (c) Smithson Inc, 2012\n";
        print "\n";
        print_usage();
        print "\n";
        print "<warn>   = Signal strength at which a warning message will be generated.\n";
        print "<crit>   = Signal strength at which a critical message will be generated.\n";
        support();
};
#  ================================================================

Магия кроется в строчке

    my %names = (
		 '4b4468fe-c310d5c8-e0ee-002481e8ae94' => 'EVA2Tb',
		 '4f3b9661-776069e9-5002-78e7d158f891' => 'EVA360Gb',
		 '54f5b0bc-97f70749-e088-f0921c1099b0' => 'NN1.5Tb',
		 '52e8d119-309fe1b1-10fb-d89d676e0ce0' => 'EVA2TB_2',
		 '55c07bc8-6365350e-c56b-3c4a92e5f7f4' => '0CLONE',
		);

её приходится править каждый раз, когда добавляется новый диск к серверам. За четыре года — аж два раза.

Как ни загадочно это покажется, но на разных vSphere-серверах, стоящих в разных кластерах, эти коды одинаковые для одного и того же диска.

Имена и шифры локальных дисков у всех серверов разные, но в нашем случае они нам не интересны, локально у нас лежат только тестовые проекты, поэтому переполнение локального хранилища нас по большому счету не волнует. Но если вам важно — то можете сюда добавить соответствия шифров и имен для локальных datastore.

Еще специфично для vmware — это информация по виртуальным машинам. Она кроется за OID 1.3.6.1.4.1.6876

Информация по ОЗУ 1.3.6.1.4.1.6876.2.1.1.5
Информация по виртуальным машинам 1.3.6.1.4.1.6876.2.1.1.6
Информация по CPU 1.3.6.1.4.1.6876.3.1.1

Этот скрипт загоняет данные в rrdtool-базу, поскольку у нас нет необходимости реагировать на изменение нагрузки ОЗУ или число виртуальных машин. Но на его основе можно сделать и плагин для nagios.

check_vmwaregetstatus.pl
#!/usr/local/bin/perl
#
# (C) by Smithson Inc, 2013
#
require "srv.list";

my $RRD    = '/usr/local/bin/rrdtool';
my $EMPTY  = ":U:U:U:U:U:U:U:U:U:U";
my @DATA = (), @VM = ();
my $OID_VMWARE = '1.3.6.1.4.1.6876';
my $OID_IF_OUT = '.1.3.6.1.2.1.31.1.1.1.6';
my $OID_IF_IN  = '.1.3.6.1.2.1.31.1.1.1.10';
##my $OID_IF_IN  = '.1.3.6.1.2.1.2.2.1.16';
my $SNMP = '/usr/local/bin/snmpwalk -v2c -c нескажу';
my $AWK  = '/usr/bin/awk';

my $FILENAME='vmware.snmpdata';
my $MEMORY_MASK    = '.6876.2.1.1.5.';
my $CORES_MASK     = '.6876.2.1.1.9.';
my $MEMSIZE_MASK   = '.6876.3.2.1';
my $CPUCOUNT_MASK  = '.6876.3.1.1';
my $POWERED_MASK  = '.6876.2.1.1.6.';
my $i;

foreach  $i (sort keys %servers) {
  my $traf= getIFinfo($i);
  my $t   = getVMinfo($i);
  $t='N'.$traf.':'.$t.$EMPTY;
  my $rrd = "$RRD update $DBPATH/$i.rrd $t";
  system("$rrd") && print "ERROR update '$DBPATH/$i.rrd': $!\n";
}
#  ================================================================
sub searchinfo { # Search info by MASK
  my $mask = shift;
  my $i, @ret = (), $r = 0;
  my $c = @DATA+1;

  for ($i = 0; $i < $c; $i++) {
    if ($DATA[$i] =~ /$mask(.+)\s*=\s*([a-zA-Z0-9]+)\:\s+(.+)$/) {
      $ret[$r] = $3; 
      $r++;
    }
  }
  return @ret;
}
#  ================================================================
sub IsVMrun { # Return 0 if VM is Off or 1 is VM is On
   my $id = shift; # VM id
   my $r = 0;

   for ($i = 0; $i < @VM; $i++) {
     if ($id == $VM[$i]) { $r = 1; last; }
   }
   return $r;
}
#  ================================================================
sub memoryinfo { # Return 2 values - Memory Allocated All & Memory Allocated on run VMs
  my $mask = $MEMORY_MASK;
  my $i, $all = 0, $used = 0;
  my $c = @DATA+1;

  for ($i = 0; $i < $c; $i++) {
    if ($DATA[$i] =~ /$mask(.+)\s*=\s*([a-zA-Z0-9]+)\:\s+(.+)$/) {
      $all = $all + $3; 
      if (IsVMrun($1) == 1) { $used = $used + $3; }
    }
  }
  $all  =  $all*1000*1000;
  $used = $used*1000*1000;
  return ($all, $used);
}
#  ================================================================
sub coresinfo { # Return 2 values - Cores Total Allocated & Cores Allocated on run VMs
  my $mask = $CORES_MASK;
  my $i, $all = 0, $used = 0;
  my $c = @DATA+1;

  for ($i = 0; $i < $c; $i++) {
    if ($DATA[$i] =~ /$mask(.+)\s*=\s*([a-zA-Z0-9]+)\:\s+(.+)$/) {
      $all = $all + $3; 
      if (IsVMrun($1) == 1) { $used = $used + $3; }
    }
  }
  return ($all, $used);
}
#  ================================================================
sub getVMinfo {
  my $ip  = shift;
  my $ret, $s, $count = 0;
  my $q = "$SNMP $ip $OID_VMWARE >$FILENAME.$ip";
  `$q`;

  $#DATA = -1;
  open(F, "<$FILENAME.$ip");
  while (defined($s=<F> )) {
    chomp $s;
    push @DATA, $s;
  }
  close F;
  unlink("$FILENAME.$ip");

  ($count, $oncount) = countVM();
  my ($memall, $memused) = memoryinfo();
  my ($corecount, $coreused) = coresinfo();
  my ($memsize)  = searchinfo($MEMSIZE_MASK);
  $memsize = ($memsize/1024)*1000000;
  my ($cpucount) = searchinfo($CPUCOUNT_MASK);
  $ret = "$count:$corecount:$cpucount:$servers{$ip}:U:U:U:U:$memsize:$memused:$oncount:$memall:$coreused";
  return $ret;
}
#  ================================================================
sub countVM { # Return 2 values - VMcount & VMUpCount; filled the @VM array by indexs of UP VMs.
  my $r = 0, $up = 0, $i, $c = @DATA+1;

  @VM = ();
  for ($i = 0; $i < $c; $i++) {
    if ($DATA[$i] =~ /$POWERED_MASK(.+)\s*=\s*([a-zA-Z0-9]+)\:\s+\"(powered.+)\"/) {
      $r++;
      my $f1 = $1, $f2 = $2, $f3 = $3;
      if ($f3 =~ /on/i) {
        $up++;
        push @VM, $f1;
      }
    }
  }
  return ($r, $up);
};
#  ================================================================
sub getIFinfo {
  my $ip = shift;
  my $i, $c = 0;
  my $q, $ret = "";

  for ($i = 1; $i < 5; $i++) {
    $q = $SNMP." $ip $OID_IF_IN\.$i | $AWK \'\{print \$4\}\'";
    my $s = `$q`;
    chomp $s;	
    if ($s =~ /^(\d+)$/) {
       $ret=$ret.":$s";
       $c++;	
    }
  };
  for ( ; $c < 4; $c++ ) {
    $ret = $ret.':U';
  }

  for ($i = 1; $i < 5; $i++) {
    $q = $SNMP." $ip $OID_IF_OUT\.$i | $AWK \'\{print \$4\}\'";
    my $s = `$q`;
    chomp $s;	
    if ($s =~ /^(\d+)$/) {
       $ret=$ret.":$s";
       $c++;	
    }
  };
  for ( ; $c < 8; $c++ ) {
    $ret = $ret.':U';
  }
  return $ret;
}


srv.lst

%servers = (
                    '10.11.1.11' => 40,'10.11.1.12' => 40,'10.11.1.14' => 40,'10.11.1.15' => 40,
                    '10.11.1.8' => 32,'10.11.1.9' => 32,'10.11.1.7' => 32,
                    '10.11.1.3' => 24,'10.11.1.6' => 24
                   );


$targetdir = '/data//rrdtool/www/vmware';
$DBPATH    = '/data/rrdtool/vmware/data';

@periodlist = ('10h', '2d', '10d', '30d', '1y', '3y');

Значения хеша — это максимальное число процессоров (ядер) на данном сервере. Мне было проще сделать так, чем вычислять это число каждый раз на основе подсчета строк ProcessorLoad или CPU Pkg/ID/Node. Число процессоров у сервера меняется ОЧЕНЬ редко :)

srv.lst — это такой общий файлик, который атачится как к скрипту мониторинга, так и к скрипту генерации html-страниц и скрипту рисования.

Теперь поговорим о Rittal CMC-TC.

Rittal CMC-TС — это разработка компании rittal для мониторинга условий среды — температуры, влажности и силы воздушного потока. Правда, датчики воздушного потока там уродские, они возвращают не скорость «ветра», а 0 — нет потока и 1 — есть поток. Причем регулируются они аналогово, штыковой отверткой крутишь вернер датчика и таким образом устанавливаешь порог срабатывания между «ветер есть» и «ветра нет». Но песня не о том.

Сама система состоит из процессорного модуля PU, к которому можно подключить до 4 (гнезда так и пронумерованы — 1, 2, 3 и 4) хабов датчиков. К каждому хабу можно подключить до 4 датчиков.

Дальше вступает в силу малопостижимая логика. Мониторинг по snmp возможен (snmp настраивается в настройках сети) и скрывается за OID .1.3.6.1.4.1.2606.4.2. Первый подключенный хаб датчиков имеет номер 3, второй — 4, третий — 5 и четвертый — 6. Все логично, не так ли? Датчики на хабе в свою очередь нумеруются 1, 2, 3 и 4, что наводит на мысль, что эти два устройства программировали разные малознакомые люди. И между номером датчика и номером хаба еще 4(!) ветки OID.

Еще раз:

.1.3.6.1.4.1.2606.4.2.3.5.2.1.5.1 — это показания первого датчика первого хаба.
.1.3.6.1.4.1.2606.4.2.6.5.2.1.5.4 — это показания четвертого датчика четвертого хаба.
И не спрашивайте меня, почему так :)

Теперь о мониторинге

define command {
  command_name check_snmp_oid
  command_line $USER1$/check_snmp -H $HOSTADDRESS$ -o $ARG1$ -C $ARG2$ -w $ARG3$ -c $ARG4$ -u $ARG5$ -l ""
}

define service{
        name                            temperature-service    
        use                             generic-service
        register                        0                       
        contact_groups                  conditions
        notification_options            c,r                     
        }

define service{
        use                     temperature-service
        host_name          	CMC-02
        service_description     Temperature Floor 12 point 1
        check_command           check_snmp_oid!.1.3.6.1.4.1.2606.4.2.5.5.2.1.5.3!секрет!20!31!C
        }

Как вы легко теперь угадаете, это опрос третьего датчика на третьем же хабе на процессорном модуле СМС-2. И судя по «С» — это температура :)

Где и как вы расположите датчики — дело ваше, не забудьте только нарисовать карту, она пригодится.

Nagios и Synology

.
У Synology есть отличные полубытовые сетевые дисковые хранилища, которые мы используем для резервного бакапа (набрать 40 Тб на synology стоит около 300 000 рублей, а на HP EVA или HP 3PAR — и трех миллионов может не хватить). Поэтому synology у нас много, дисков в них тоже не мало и за всем этим надо следить. А вы как хотели?

Внутри у Synology обычный linux (работает ssh, можно ставить пакеты, например, rrdtool или mc, работает rsync) и отвечает на snmp-запросы о 25 ветке (OID 1.3.6.1.2.1.25).
Дополнительно у него можно узнать состояние дисков и их температуру.

get-synologydisks.pl
#!/usr/local/bin/perl
#
# (C) Smithson Inc
#
#

#use strict;
use lib "/usr/local/libexec/nagios";
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use vars qw($PROGNAME);
use Getopt::Long;
use  Time::gmtime;
use vars qw($opt_V $opt_h $verbose $opt_w $opt_c $opt_H $volname $opt_mode $mode);

$PROGNAME = `basename $0`;

Getopt::Long::Configure('bundling');
GetOptions
        ("V"   => \$opt_V, "version"    => \$opt_V,
         "h"   => \$opt_h, "help"       => \$opt_h,
         "m=s" => \$opt_mode, "mode=s" => \$opt_mode,
         "w=s" => \$opt_w, "warning=s"  => \$opt_w,
         "c=s" => \$opt_c, "critical=s" => \$opt_c,
         "H=s" => \$opt_H, "hostname=s" => \$opt_H);


if ($opt_V) {
        print_revision($PROGNAME,''); #'
        exit $ERRORS{'OK'};
}

if ($opt_h) {
        print_help();
        exit $ERRORS{'OK'};
}

$opt_H = shift unless ($opt_H);
my $host = $1 if ($opt_H =~ m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z][-a-zA-Z0]+(\.[a-zA-Z][-a-zA-Z0]+)*)$/);
if (!(defined($host))) { print_help();  exit $ERRORS{'ERROR'}; };

($opt_c) || ($opt_c = shift) || ($opt_c = 4);
my $critical = $1 if ($opt_c =~ /([0-9]+)/);

($opt_w) || ($opt_w = shift) || ($opt_w = 3);
my $warning = $1 if ($opt_w =~ /([0-9]+)/);

#
# $mode == 0 - get temperature
# $mode == 1 - get disk status
#
my $mode = 0;
($opt_mode) || ($opt_mode = shift) || ($opt_mode = 'temp');
if ($opt_mode =~ /status/i) { $mode = 1; }

$code = $ERRORS{'OK'};

my $community = 'public';
my $snmpwalk 	  = "/usr/local/bin/snmpwalk -v 2c -c $community -t 15";

my $OID_DISKSTAT   = '.1.3.6.1.4.1.6574.2.1.1.5';
my $OID_DISKTEMP   = '.1.3.6.1.4.1.6574.2.1.1.6';

my $n = '';

if ($mode == 0) { # get  temp
   $n = $OID_DISKTEMP;
}
if ($mode == 1) { # get  status
   $n = $OID_DISKSTAT;
}
$n = getsyn($host, $n);
print "Results: $n\n";
   
exit ($code);

#  ================================================================
sub getsyn {
  my $ip = shift, $s, $ret;
  my $OID = shift;

  my @D = getSNMPwalk($ip, $OID);
  foreach $s (@D) {
   if ($s eq 'U') { exit $ERRORS{'ERROR'}; };
   if (length($ret) > 0) { $ret = $ret.", "; }
   if ($s =~ /\= INTEGER\: (\d+)/) {
     $ret = $ret."$1";
     if ($1 >= $critical) { $code = $ERRORS{'CRITICAL'}; }
     if ($code != $ERRORS{'CRITICAL'} && $1 >= $warning) { $code = $ERRORS{'WARNING'}; }
   }
  }
  return $ret;
}
#  ================================================================
sub getSNMPwalk {
  my $ip  = shift;
  my $snmpquery = shift;
  my $q, @dat;

  $q = "$snmpwalk $ip $snmpquery";
  @dat = `$q`;
  if ($#dat < 1) {
    return ('U');
 }
  return @dat;
}
#  ================================================================
sub print_usage () {
        print "Usage: $PROGNAME -H <host> [-v <volumename>] [-w <warn>] [-c <crit>] [-m <mode>]\n";
	
}
#  ================================================================
sub print_help () {
        print_revision($PROGNAME,'');
        print "Copyright (c) Smithson Inc, 2011\n";
        print "\n";
        print_usage();
        print "\n";
        print "<warn>   = Signal strength at which a warning message will be generated.\n";
        print "<crit>   = Signal strength at which a critical message will be generated.\n";
        print "<mode>   = temp or status. Default is temp.\n\t Temp return temperature of disks\n\t Status return disks status in RAID. Correct status is 1";\n";
};
#  ================================================================

с параметром mode=temp (по умолчанию) возвращает температуру всех дисков, с параметром mode=status — состояние дисков в RAID. 1 Normal, 2 Initialized, 3 Not Initialized, 4 System Partition Failed, 5 Crashed. Соответсвенно 1 — нормально, 2 — warning, всё прочее — critical.

Для опроса synology, у которых много дисков, надо увеличивать timeout (здесь стоит 15 секунд), иначе она не успевает ответить.

Nagios и ИБП


У нас стоят бесперебойники двух производителей — APC и Chloride. Начнём с APC. Специфичная для этих ИБП информация скрывается за OID .1.3.6.1.4.1.318. Что можно извлечь? Много — ttl (сколько еще протянет ups при текущей нагрузке, если прямо сейчас отнять у него электричество), работает ups от батарей или нет, надо ли менять батареи, какая температура внутри ящика, входной-выходной вольтаж, уровень заряда.

мониторинг APC

define service{
        name                            ups-service
        active_checks_enabled           1
        passive_checks_enabled          1
        parallelize_check               1
        obsess_over_service             1
        check_freshness                 0
        notifications_enabled           1
        event_handler_enabled           1
        flap_detection_enabled          1
        failure_prediction_enabled      1
        process_perf_data               1
        retain_status_information       1
        retain_nonstatus_information    1
        is_volatile                     0
        check_period                    24x7
        max_check_attempts              3
        normal_check_interval           5
        retry_check_interval            1
        notification_options            c,r
        notification_interval           120
        notification_period             24x7
         register                        0
	contact_groups			admins,power-admins
        }

#APC Battery needs replacement
define service{
        use                     ups-service
        hostgroup_name	        APC-smart
        service_description     APC Battery needs replacement
        check_command           check_snmp!-o .1.3.6.1.4.1.318.1.1.1.2.2.4.0 -C XXXX -c 2
        notification_period     workhours
}

#APC status
define service{
        use                     ups-service
        hostgroup_name	        APC-smart
        service_description     APC status
        check_command           check_snmp!-o .1.3.6.1.4.1.318.1.1.1.4.1.1.0 -C XXXX -c 2
	contact_groups          smsgroup,power-admins
}

#APC Battery temperature
define service{
        use                     ups-service
        hostgroup_name          APC-smart
        service_description     APC Battery temperature
        check_command           check_snmp!-o .1.3.6.1.4.1.318.1.1.1.2.2.2.0 -C XXXX -w 38 -c 45 -u C
	contact_groups          smsgroup,power-admins
        notification_period     workhours
}

# APC worktime 100 ticks * 60 sec
define service{
        use                     ups-service
        hostgroup_name	        APC-smart
        service_description     APC WorkTime
        check_command           check_snmp!-o .1.3.6.1.4.1.318.1.1.1.2.2.3.0 -C XXXX -w 120000: -c 18000: -u sec*100
	contact_groups          power-admins
        notification_period     workhours
}


ttl APC возвращает в 1/100 секунды (ну удобно ему так) и для получения минут его надо делить на 6000. Думаю, такой скрипт в одну строчку сможете сочинить и сами.

Из Chloride извлечь можно меньше. Ttl, температуру, состояние (батареи или электросеть), входной-выходной вольтаж, уровень заряда. Отвечает он на стандартный OID для UPS ( 1.3.6.1.2.1.33).

nagios+chloride

define service{
        use                     ups-service
        host_name	        UPS1, UPS2
        service_description     Chloride temperature
        check_command           check_snmp!-o 1.3.6.1.2.1.33.1.2.7.0 -C public -w 38 -c 45 -u C
	contact_groups          smsgroup,power-admins
}

define service{
        use                     ups-service
        host_name	        UPS1, UPS2
        service_description     Chloride WorkTime
        check_command           check_snmp!-o 1.3.6.1.2.1.33.1.2.3.0 -C public  -w 23: -c 11: -u min
	contact_groups          smsgroup,power-admins
}


Зато время работы (ttl) он возвращает в минутах, а не в сотых секунды, как APC!
Кроме того, поскольку chloride «кластерные» ИБП (обученные работать на одну нагрузку вдвоём), то у них еще можно получать информацию по фазам входного и выходного сигнала. Толку от этого, правда, мало.

Поскольку с UPS шутки плохи, то информация о неполадках в них дублируется на sms нескольким особо везучим сотрудникам.

Принтеры


Сразу скажу, nagios у меня сами принтеры не мониторит. Мы мониторим очереди iPrint, это удобнее. Но для удобства учёта и предсказания расхода картриджей у нас висит скрипт, который рисует в rrdtool графики расхода картриджей и бумаги. Можно за определенный период понять, пользуются принтером или нет (300 листов за год — можно снимать :)), как расходуются картриджи, какие там картриджи и т.д.

printers_get_snmp.pl
#!/usr/bin/perl
#
# (C) Smithson Inc, 2008
#
require "prn.list";

$RRD    = '/usr/local/bin/rrdtool';
$DBNAME = '/data/rrdtool/printers/prn-pagecount.rrd';
$AWK    = '/usr/bin/awk';

@DATA = ();

for ($i=1;  $i < 255; $i++) {
  $DATA{$i} = 'U';
}

foreach  $i (@usedip) {
  $DATA{$i} = getPageCount($i);
}

$s = 'N';
for ($i=1;  $i < 255; $i++) {
  $s = $s.":$DATA{$i}";
}

$rrd = "$RRD update $DBNAME $s";
#print "$rrd \n";
system("$rrd") && print "ERROR update '$DBNAME': $!\n";


sub getPageCount {
      my $ip = shift;

#print "$ip\n";
      $q = "/usr/local/bin/snmpget -v1 -c public 192.168.0.$ip 1.3.6.1.2.1.43.10.2.1.4.1.1 | $AWK \'\{print \$4\}\'";
      $dat = `$q`;
      chomp $dat;
      if ($dat =~ /[A-z]/) { $dat = 'U' };
      if (length($dat) < 1) { $dat = 'U' };
      return $dat
}


toners_get_snmp.pl
#!/usr/bin/perl
#
# (C) Smithson Inc, 2008
#

use Encode;

require "prn.list";

$RRD    = '/usr/local/bin/rrdtool';
$DBNAME = '/data/rrdtool/printers/prn-toner.rrd';

$SNMP = '/usr/local/bin/snmpget -v1';

$INDEXMAX = 6;
$TONERDEBUG  = '';
@TONERSTATUS = ();
@PRNNAME     = ();
@TONERNAME   = ();

for ($i=1;  $i < 256; $i++) {
  $TONERNAME[$i] = "X";
  $TONERSTATUS[$i] = 'U:U:U:U:U:U';
  $PRNNAME[$i] = "";
}

LoadPRNData($PRNINFOfile);

foreach  $i (@usedip) {
  getPRNInfo($i);
}

$t = 'N';
for ($i=1;  $i < 256; $i++) {
  $t = $t.":$TONERSTATUS[$i]";
}

# --------- debug !
#print "$t\n";
#print "$TONERDEBUG\n";
# --------------- !
$rrd = "$RRD update $DBNAME $t";
my $q = `$rrd`;
if (length($q) > 2) { print "ERROR update '$DBNAME': $q\n"; }

StorePRNData($PRNINFOfile);


sub getPRNInfo {
      my $ip = shift;
      my ($res,$r) = '';
      my ($i,$k);
      my ($dat) = '';
      
      my ($tonerstatus) = '1.3.6.1.2.1.43.11.1.1.9.1';
      my ($tonername)   = '1.3.6.1.2.1.43.11.1.1.6.1'; 
      my ($prnname)     = '1.3.6.1.2.1.1.5.0';


      $q = "$SNMP -c public 192.168.0.$ip $prnname | awk \'\{print \$4\}\'";
      $dat = `$q`;
      chomp $dat;
# ------------ debug ----------------
#print "$ip (prnname) = $dat \n";
      if (length($dat) < 1) { return }
      $PRNNAME[$ip] = $dat;

      $k = 1;
      for ($i = 1; $i <= $INDEXMAX; $i++) {
          $q = "$SNMP  -c public 192.168.0.$ip $tonerstatus.$i | awk \'\{print \$4\}\'";
	  $dat = `$q`;
          chomp $dat;
# ------------ debug ----------------
#print "$ip (status) = $dat \n";
	  if (length($dat) < 1) { last }
	  if ($dat < 0) {
	    if ($dat == -3) { $dat = 100 };
	    if ($dat == -2) { $dat =  30 };
	    if ($dat == -1) { $dat =  10 };
	  }
          if (length($res) < 1) { $res = $dat }
          else { $res = $res.":$dat"; }
          $k++;

          $q = "$SNMP  -c public 192.168.0.$ip $tonername.$i";
	  $dat = `$q`;
          chomp $dat;
          $dat =~ s/\n//g;
# ------------ debug ----------------
#print "$ip (name) = $dat \n";
	  if ($dat =~ /STRING: \"(.+)\"/) {
            $r = $r."$1 |";
    	  }
	  elsif ($dat =~ /Hex-STRING: (.+)/) {
#print "$dat\n";
	    my $s = getStringH($1);
	    $r = $r."$s |";
	  }
          else { last };    
      }
      
      Encode::from_to($r, 'utf-8', 'windows-1251');
      $r =~ s/\?+//g;

      for ($i = $k; $i<= $INDEXMAX; $i++) {
        if (length($res) < 1) { $res = 'U' }
        else { $res = $res.':U'; }
      }
      
      $TONERSTATUS[$ip] = $res;
      $TONERNAME[$ip]   = $r;

      $TONERDEBUG = $TONERDEBUG."$ip: $res \n";
}

sub StorePRNData {
    my $filename = shift;
    my $i;
    my $s = "";
    
    foreach $i (@usedip) {
      if ((length($PRNNAME[$i]) > 2) && (length($TONERNAME[$i]) > 2)) { 
        $s = $s."$i = ";
        $s = $s."$PRNNAME[$i] / ";
        $s = $s."$TONERNAME[$i]";
        $s = $s."\n";
      }
    }
    open (F, ">$HOME/$filename");
    print F $s;
    close F;
}

sub getStringH {
    my $s = shift;
    
    $s =~ s/(00)//egi;
    $s =~ s/([0-9a-f][0-9a-f])/chr(hex($1))/egi;
    $s =~ s/ //g;
    
    return $s;
}


toners_draw.pl
#!/usr/bin/perl
#
# (C) Smithson Inc, 2008
#


use Encode;
require "prn.list";

$RRD    = '/usr/local/bin/rrdtool';
$DBNAME = '/data/rrdtool/printers/prn-toner.rrd';
$IMGPATH= '/data/rrdtool/www/printers/img';

my $BLACK  = '#000000';
my $YELLOW = '#FFFF00';
my $CYAN   = '#00CCFF';
my $MAGENTA= '#EE00EE';
my $FUSION = '#00CC00';
my $FUSION1= '#CC0000';
my $TICKCOLOR = '#888888';

$INDEXMAX = 6;
@TONERSTATUS = ();
@PRNNAME     = ();
@TONERNAME   = ();

LoadPRNData ($PRNINFOfile);
DrawToners(340, 230, "-48h");
foreach $j (@periodlist) {
    DrawToners(500, 300, "-$j");
}

sub DrawBlack {
# рисует картинку для черно-белого принтера - первый параметр - картридж, второй - печка
  my $ip = shift; # ip address
  my $width  = shift;
  my $height = shift;
  my $period = shift;
  
  my ($toner, $fusion) = split (/ \|/, $TONERNAME[$ip]);
  
  my $title = $PRNNAME[$ip];
# Если имя есть, то используется оно, иначе используется ip
# Если имя есть и картинка широкая, то к имени добавляется ещё и ip
  if (length($title) < 2) { $title = "192.168.0.$ip" }
  elsif ($width > 350) { $title = $title." (192.168.0.$ip)" }
  
  my $q = "$RRD graph $IMGPATH/toner".$ip.$period.".png ";
  $q = $q."--start $period --end now   ";
  $q = $q."--width $width --height $height ";
  $q = $q."--full-size-mode ";
  $q = $q."--title \"$title Info ($period)\" "; 
  if ((length($fusion) > 31) && ($width < 350)) {
      $fusion = substr($fusion, 0, 29)."...";
  }
#  else { $q = $q."--lazy "; }
  $q = $q."--lower-limit 0 ";
  $q = $q." DEF:p01=$DBNAME:prn".$ip."toner1status:AVERAGE     ";
  $q = $q." CDEF:pp1=p01 ";
  $q = $q." CDEF:z1=p01,0,EQ ";
  if (length($fusion) > 1) {
    $q = $q." DEF:p02=$DBNAME:prn".$ip."toner2status:AVERAGE     ";
  } else { $q = $q."CDEF:p02=p01,0,\* "; }
    $q = $q." CDEF:fusion1=p02,0.01,\*      ";
    $q = $q." CDEF:fusion=0,fusion1,-  ";
#  }
  $q = $q." AREA:p01$BLACK:\"$toner \" ";
  $q = $q."GPRINT:pp1:LAST:\"%0.0lf\\j\"  ";
  $q = $q." TICK:z1$TICKCOLOR:1 ";
#  if (length($fusion) > 1) {
    $q = $q." LINE3:fusion$FUSION:\"$fusion\" ";
    $q = $q."GPRINT:p02:LAST:\"%0.0lf\\j\"";   
#  }

  my $dat = `$q`;


}

sub DrawColor {
# рисует картинку для цветного принтера - первый параметр - черный картридж, второй-третий-четвертый - цветные
# пятый-шестой - печка
  my $ip = shift; # ip address
  my $width  = shift;
  my $height = shift;
  my $period = shift;
  
  
  my ($color0, $color1, $color2, $color3) = ($BLACK, $CYAN, $MAGENTA, $YELLOW);
  
  my ($toner0, $toner1, $toner2, $toner3, $fusion, $fusion1) = split (/ \|/, $TONERNAME[$ip]);
  ($color0, $color1, $color2, $color3) = SortColors(($toner0, $toner1, $toner2, $toner3));
  
  my $title = $PRNNAME[$ip];
# Если имя есть, то используется оно, иначе используется ip
# Если имя есть и картинка широкая, то к имени добавляется ещё и ip
  if (length($title) < 2) { $title = "192.168.0.$ip" }
  elsif ($width > 350) { $title = $title." (192.168.0.$ip)" }
  
  my $q = "$RRD graph $IMGPATH/toner".$ip.$period.".png ";
  $q = $q."--start $period --end now   ";
  $q = $q."--width $width --height $height ";
  $q = $q."--full-size-mode ";
  $q = $q."--title \"$title Info ($period)\" "; 
  if ((length($fusion1) > 31) && ($width < 350)) {
    $fusion1 = substr($fusion1, 0, 29)."...";
  }
  else { $q = $q."--lazy "; }
  $q = $q."--lower-limit 0 ";
  $q = $q." DEF:p01=$DBNAME:prn".$ip."toner1status:AVERAGE     ";
  $q = $q." DEF:p02=$DBNAME:prn".$ip."toner2status:AVERAGE     ";
  $q = $q." DEF:p03=$DBNAME:prn".$ip."toner3status:AVERAGE     ";
  $q = $q." DEF:p04=$DBNAME:prn".$ip."toner4status:AVERAGE     ";
  $q = $q." CDEF:pp1=p01 ";
  $q = $q." CDEF:pp2=p02 ";
  $q = $q." CDEF:pp3=p03 ";
  $q = $q." CDEF:pp4=p04 ";
  $q = $q." CDEF:z1=p01,0,EQ ";
  $q = $q." CDEF:z2=p02,0,EQ ";
  $q = $q." CDEF:z3=p03,0,EQ ";
  $q = $q." CDEF:z4=p04,0,EQ ";
  $q = $q." CDEF:z5=z1,z2,+ ";
  $q = $q." CDEF:z6=z5,z3,+ ";
  $q = $q." CDEF:z0=z6,z4,+ ";
  $q = $q." DEF:p05=$DBNAME:prn".$ip."toner5status:AVERAGE     ";
  $q = $q." CDEF:fusion0=p05,0.1,\*      ";
  $q = $q." CDEF:fusion=0,fusion0,-  ";
  $q = $q." DEF:p06=$DBNAME:prn".$ip."toner6status:AVERAGE     ";
  $q = $q." CDEF:fusion2=p06,0.1,\*      ";
  $q = $q." CDEF:fusion1=0,fusion2,-  ";
  $q = $q." TICK:z0$TICKCOLOR:1 ";
  $q = $q."  AREA:pp1$color0:\"$toner0\" ";
  $q = $q."GPRINT:pp1:LAST:\"%0.0lf\\j\"  ";
  $q = $q." STACK:pp2$color1:\"$toner1\"  ";
  $q = $q."GPRINT:pp2:LAST:\"%0.0lf\\j\"  ";
  $q = $q." STACK:pp3$color2:\"$toner2\" ";
  $q = $q."GPRINT:pp3:LAST:\"%0.0lf\\j\"   ";
  $q = $q." STACK:pp4$color3:\"$toner3\" ";
  $q = $q."GPRINT:pp4:LAST:\"%0.0lf\\j\"   ";
  if  (length($fusion) > 1) {
    $q = $q." LINE3:fusion$FUSION:\"$fusion\" ";
    $q = $q."GPRINT:p05:LAST:\"%0.0lf\\j\"   ";
  }

  if  (length($fusion1) > 1) {
    $q = $q." LINE3:fusion1$FUSION1:\"$fusion1\" ";
    $q = $q."GPRINT:p06:LAST:\"%0.0lf\\j\"   ";
  }

  my $dat = `$q`;

}
        
sub SortColors {
# принимает массив названий картриджей и возвращает список цветов согласно цветам картриджей
  my @list = @_;
  my (@c) = ($BLACK, $CYAN, $MAGENTA, $YELLOW);
  my $i;
  
  for ($i = 0; $i < 4; $i++) {
    my $s = $list[$i];
    if ($s =~ /Black/i) {
      $c[$i] = $BLACK;
    }
    if ($s =~ /Cyan/i) {
      $c[$i] = $CYAN;
    }
    if ($s =~ /Magent/i) {
      $c[$i] = $MAGENTA;
    }
    if ($s =~ /Yellow/i) {
      $c[$i] = $YELLOW;
    }
  }
  
  return @c;
}

sub DrawToners {
# рисует весь массив принтеров, разделяя их на цветные или черно-белые
  my $width  = shift;
  my $height = shift;
  my $period = shift;
  
  my ($i);
  my ($name);
  
  foreach $i (@usedip) {
    $name = $TONERNAME[$i];
    my @aa = split(/\|/, $name);
    my $qa =  $#aa+1;
#print "$i = $qa ($name) \n";     
    if ($qa > 3) {
      DrawColor($i, $width, $height, $period);
    }
    else {
      DrawBlack($i, $width, $height, $period);
    }
  };
}


prn.list

@usedip = (
	    115,
	    122,124,125,128,129,
	    131,132,136,137,
	    140,141,142,145,147,148,
	    150,152,155,157,
	    160,162,164,165,166,167,168,169,
	    171,172,173,174,175,176,177,179,
	    180,182,183,185,186,188,189,
	    190,191,192,194,195,197,198,199,
	    201,202,203,205,207,208,209,
	    210,212,215,216,217,219,
	    220,225,
	    235,236,237,
	    241,245
	  );
	  
                   

@periodlist = ('14h', '2d', '10d', '30d', '1y');

$HOME = '/data/rrdtool/printers';

$PRNINFOfile = 'prn.info';

sub LoadPRNData {
    my $filename = shift;
    my $i;
    my $s = "";
    my @data;

    open (F, "$HOME/$filename");
    @data=<F>;
    close F;

    foreach $s (@data) {
      if ($s =~ /(\d+) = (.+) \/ (.*)/) {
        $i = $1;
        $PRNNAME[$i] = $2;
        $TONERNAME[$i] = $3;
      }
    }
}


Суть этой магии в том, что при рисовании данные берутся из заранее сформированного файла вида:

prn.info

216 = NPI93F7F3 / Black Cartridge HP CE278A |
219 = NPID6E096 / Black Cartridge HP CE278A |
220 = HOZY / Black Print Cartridge HP Q1339A |Maintenance Kit HP 110V-Q2436A, 220V-Q2437A |
225 = NPIBAA4EA / Toner Cartridge HP C4127X |
235 = Ditat_HP4700_Color / Black Cartridge HP Q5950A |Cyan Cartridge HP Q5951A |Magenta Cartridge HP Q5953A |Yellow Cartridge HP Q5952A |Image Transfer Kit HP Q7504A |Image Fuser Kit HP 110V-Q7502A, 220V-Q7503A |
237 = NPI7C543C / BlackCartridgeHPCC364X |MaintenanceKitHP110V-CB388A,220V-CB389A |
241 = WorkCentre / Black Toner Cartridge |Yellow Toner Cartridge |Magenta Toner Cartridge |Cyan Toner Cartridge |Waste Toner Container |
245 = NPI0E8A4D / Black Print Cartridge HP C8543X |Maintenance Kit HP 110V-C9152A, 220V-C9153A |


При большом количестве принтеров это сильно снижает нагрузку как на «рисовальный» сервер, так и на сами принтеры.

И немного о Cisco


Да, мониторинг cisco через nagios стандартнее некуда. Но есть нюанс. Как всегда, при интенсивной работе routers и switchs обрастают новыми интерфейсами. Туннели, vlans и прочие подинтерфейсы возникают и накапливаются, их ставят на мониторинг и всё вроде бы отлично. Но потом случается она, перезагрузка — и вдруг выясняется, что при старте cisco отсортировала интерфейсы по типам, а внутри типов — по номерам и те номера в дереве mibs, которые вы обнаруживали и прописывали в мониторинге — уже не те.

Во избежание надо мониторить по имени интерфейса. Стандартный плагин нагиоса вроде даже умеет — но не делает этого. Не находит он интерфейс по имени. Хотя имя типа Tunnel40 — куда уж уникальнее.

check_iftraf
#! /usr/local/bin/perl -w


use POSIX;
use strict;
use lib "/usr/local/libexec/nagios" ;
use utils qw($TIMEOUT %ERRORS &print_revision &support);

use Net::SNMP;
use Getopt::Long;
&Getopt::Long::config('bundling');

my $PROGNAME = "check_iftraf";
sub print_help ();
sub usage ($);
sub print_usage ();
sub process_arguments ();

my $timeout;
my $status;
my %ifOperStatus = 	('1','up',
                     '2','down',
                     '3','testing',
                     '4','unknown',
                     '5','dormant',
                     '6','notPresent',
                     '7','lowerLayerDown');  # down due to the state of lower layer interface(s)

my $state = "UNKNOWN";
my $answer = "";
my $snmpkey = 0;
my $community = "public";
my $maxmsgsize = 1472 ; # Net::SNMP default is 1472
my ($seclevel, $authproto, $secname, $authpass, $privpass, $privproto, $auth, $priv, $context);
my $port = 161;
my @snmpoids;
my $sysUptime        = '1.3.6.1.2.1.1.3.0';
my $snmpIfDescr      = '1.3.6.1.2.1.2.2.1.2';
my $snmpIfType       = '1.3.6.1.2.1.2.2.1.3';
my $snmpIfAdminStatus = '1.3.6.1.2.1.2.2.1.7';
my $snmpIfOperStatus = '1.3.6.1.2.1.2.2.1.8';
my $snmpIfName       = '1.3.6.1.2.1.31.1.1.1.1';
my $snmpIfLastChange = '1.3.6.1.2.1.2.2.1.9';
my $snmpIfAlias      = '1.3.6.1.2.1.31.1.1.1.18';
my $snmpLocIfDescr   = '1.3.6.1.4.1.9.2.2.1.1.28';
my $snmpMIBIN   = '1.3.6.1.2.1.2.2.1.10';
my $snmpMIBOUT  = '1.3.6.1.2.1.2.2.1.16';
my $hostname;
my $ifName;
my $session;
my $error;
my $response;
my $snmp_version = 2 ;
my $ifXTable;
my $opt_h ;
my $opt_V ;
my $ifdescr;
my $iftype;
my $key;
my $lastc;
my $dormantWarn;
my $adminWarn;
my $name;
my %session_opts;

### Validate Arguments

$status = process_arguments();


# Just in case of problems, let's not hang Nagios
$SIG{'ALRM'} = sub {
	print ("ERROR: U U No snmp response from $hostname (alarm)\n");
	exit $ERRORS{"UNKNOWN"};
};

alarm($timeout);

($session, $error) = Net::SNMP->session(%session_opts);

		
if (!defined($session)) {
			$state='UNKNOWN';
			$answer=' U U '.$error;
			print ("$state: $answer\n");
			exit $ERRORS{$state};
}

## map ifdescr to ifindex - should look at being able to cache this value

if (defined $ifdescr || defined $iftype) {
	# escape "/" in ifdescr - very common in the Cisco world
	if (defined $iftype) {
		$status=fetch_ifindex($snmpIfType, $iftype);
	} else {
		$ifdescr =~ s/\//\\\//g;
		$status=fetch_ifindex($snmpIfDescr, $ifdescr);  # if using on device with large number of interfaces
		                                                # recommend use of SNMP v2 (get-bulk)
	}
	if ($status==0) {
		$state = "UNKNOWN";
		printf "$state: U U could not retrive ifdescr/iftype snmpkey - $status-$snmpkey\n";
		$session->close;
		exit $ERRORS{$state};
	}
}


## Main function

$snmpIfAdminStatus = $snmpIfAdminStatus . "." . $snmpkey;
$snmpIfOperStatus = $snmpIfOperStatus . "." . $snmpkey;
$snmpIfDescr = $snmpIfDescr . "." . $snmpkey;
$snmpIfName	= $snmpIfName . "." . $snmpkey ;
$snmpIfAlias = $snmpIfAlias . "." . $snmpkey ; 
$snmpMIBIN = $snmpMIBIN . "." . $snmpkey ; 
$snmpMIBOUT = $snmpMIBOUT . "." . $snmpkey ; 

push(@snmpoids,$snmpIfAdminStatus);
push(@snmpoids,$snmpIfOperStatus);
push(@snmpoids,$snmpIfDescr);
push(@snmpoids,$snmpIfName) if (defined $ifXTable) ;
push(@snmpoids,$snmpIfAlias) if (defined $ifXTable) ;
push(@snmpoids,$snmpMIBIN);
push(@snmpoids,$snmpMIBOUT);

if (!defined($response = $session->get_request(@snmpoids))) {
	$answer=$session->error;
	$session->close;
	$state = 'WARNING';
	print ("$state: SNMP error: $answer\n");
	exit $ERRORS{$state};
}

$answer = sprintf("host '%s', %s(%s) is %s\n", 
	$hostname, 
	$response->{$snmpIfDescr},
	$snmpkey, 
	$ifOperStatus{$response->{$snmpIfOperStatus}}
);


## Check to see if ifName match is requested and it matches - exit if no match
## not the interface we want to monitor
if ( defined $ifName && not ($response->{$snmpIfName} eq $ifName) ) {
	$state = 'UNKNOWN';
	$answer = "U U Interface name ($ifName) doesn't match snmp value ($response->{$snmpIfName}) (index $snmpkey)";
	print ("$state: $answer\n");
	exit $ERRORS{$state};
} 

## define the interface name
if (defined $ifXTable) {
	 $name = $response->{$snmpIfName} ." - " .$response->{$snmpIfAlias} ; 
}else{
	 $name = $response->{$snmpIfDescr} ;
}

## if AdminStatus is down - some one made a consious effort to change config
##
if ( not ($response->{$snmpIfAdminStatus} == 1) ) {
	$answer = "Interface $name (index $snmpkey) is administratively down.";
	if ( not defined $adminWarn or $adminWarn eq "w" ) {
		$state = 'WARNING';
	} elsif ( $adminWarn eq "i" ) {
		$state = 'OK';
	} elsif ( $adminWarn eq "c" ) {
		$state = 'CRITICAL';
	} else { # If wrong value for -a, say warning
		$state = 'WARNING';
	}
} 
## Check operational status
elsif ( $response->{$snmpIfOperStatus} == 2 ) {
	$state = 'CRITICAL';
	$answer = "U U Interface $name (index $snmpkey) is down.";
} elsif ( $response->{$snmpIfOperStatus} == 5 ) {
	if (defined $dormantWarn ) {
		if ($dormantWarn eq "w") {
			$state = 'WARNING';
			$answer = "U U Interface $name (index $snmpkey) is dormant.";
		}elsif($dormantWarn eq "c") {
			$state = 'CRITICAL';
			$answer = "U U Interface $name (index $snmpkey) is dormant.";
		}elsif($dormantWarn eq "i") {
			$state = 'OK';
			$answer = "U U Interface $name (index $snmpkey) is dormant.";
		}
	}else{
		# dormant interface - but warning/critical/ignore not requested
		$state = 'CRITICAL';
		$answer = "U U Interface $name (index $snmpkey) is dormant.";
	}
} elsif ( $response->{$snmpIfOperStatus} == 6 ) {
	$state = 'CRITICAL';
	$answer = "U U Interface $name (index $snmpkey) notPresent - possible hotswap in progress.";
} elsif ( $response->{$snmpIfOperStatus} == 7 ) {
	$state = 'CRITICAL';
	$answer = "U U Interface $name (index $snmpkey) down due to lower layer being down.";
} elsif ( $response->{$snmpIfOperStatus} == 3 || $response->{$snmpIfOperStatus} == 4  ) {
	$state = 'CRITICAL';
	$answer = "U U Interface $name (index $snmpkey) down (testing/unknown).";
} else {
	$state = 'OK';
	my $inQ  = $response->{$snmpMIBIN};
	my $outQ = $response->{$snmpMIBOUT};
	$answer = "$inQ $outQ";
}



print ("$state: $answer\n");
exit $ERRORS{$state};


### subroutines

sub fetch_ifindex {
	my $oid = shift;
	my $lookup = shift;


	if (!defined ($response = $session->get_table($oid))) {
		$answer=$session->error;
		$session->close;
		$state = 'CRITICAL';
		printf ("$state: SNMP error with snmp version $snmp_version ($answer)\n");
		$session->close;
		exit $ERRORS{$state};
	}
	
	foreach $key ( keys %{$response}) {
		if ($response->{$key} =~ /$lookup/) {
			$key =~ /.*\.(\d+)$/;
			$snmpkey = $1;
			#print "$lookup = $key / $snmpkey \n";  #debug
		}
	}
	unless (defined $snmpkey) {
		$session->close;
		$state = 'CRITICAL';
		printf "$state: Could not match $ifdescr on $hostname\n";
		exit $ERRORS{$state};
	}
	
	return $snmpkey;
}

sub usage($) {
	print "$_[0]\n";
	print_usage();
	exit $ERRORS{"UNKNOWN"};
}

sub print_usage() {
	printf "\n";
	printf "usage: \n";
	printf "check_iftraf -d <IF_NAME or IF_DESC> -H <HOSTNAME> [-C <community>]\n";
	printf "Copyright (C) 2000 Christoph Kron\n";
	printf "check_iftraf.pl comes with ABSOLUTELY NO WARRANTY\n";
	printf "This programm is licensed under the terms of the ";
	printf "GNU General Public License\n(check source code for details)\n";
	printf "\n\n";
}

sub print_help() {
	print_revision($PROGNAME, '');
	print_usage();
	printf "check_iftraf plugin for Nagios monitors operational \n";
	printf "status of a particular network interface on the target host\n";
	printf "\nUsage:\n";
	printf "   -H (--hostname)   Hostname to query - (required)\n";
	printf "   -C (--community)  SNMP read community (defaults to public,\n";
	printf "                     used with SNMP v1 and v2c\n";
	printf "   -v (--snmp_version)  1 for SNMP v1 (default)\n";
	printf "                        2 for SNMP v2c\n";
	printf "                        SNMP v2c will use get_bulk for less overhead\n";
	printf "                        if monitoring with -d\n";
	printf "   -L (--seclevel)   choice of \"noAuthNoPriv\", \"authNoPriv\", or	\"authPriv\"\n";
	printf "   -U (--secname)    username for SNMPv3 context\n";
	printf "   -c (--context)    SNMPv3 context name (default is empty string)\n";
	printf "   -A (--authpass)   authentication password (cleartext ascii or localized key\n";
	printf "                     in hex with 0x prefix generated by using \"snmpkey\" utility\n"; 
	printf "                     auth password and authEngineID\n";
	printf "   -a (--authproto)  Authentication protocol (MD5 or SHA1)\n";
	printf "   -X (--privpass)   privacy password (cleartext ascii or localized key\n";
	printf "                     in hex with 0x prefix generated by using \"snmpkey\" utility\n"; 
	printf "                     privacy password and authEngineID\n";
	printf "   -P (--privproto)  privacy protocol (DES or AES; default: DES)\n";
	printf "   -k (--key)        SNMP IfIndex value\n";
	printf "   -d (--descr)      SNMP ifDescr value\n";
	printf "   -T (--type)       SNMP ifType integer value (see http://www.iana.org/assignments/ianaiftype-mib)\n";
	printf "   -p (--port)       SNMP port (default 161)\n";
	printf "   -I (--ifmib)      Agent supports IFMIB ifXTable. Do not use if\n";
	printf "                     you don't know what this is. \n";
	printf "   -n (--name)       the value should match the returned ifName\n";
	printf "                     (Implies the use of -I)\n";
	printf "   -w (--warn =i|w|c) ignore|warn|crit if the interface is dormant (default critical)\n";
	printf "   -D (--admin-down =i|w|c) same for administratively down interfaces (default warning)\n";
	printf "   -M (--maxmsgsize) Max message size - usefull only for v1 or v2c\n";
	printf "   -t (--timeout)    seconds before the plugin times out (default=$TIMEOUT)\n";
	printf "   -V (--version)    Plugin version\n";
	printf "   -h (--help)       usage help \n\n";
	printf " -k or -d or -T must be specified\n\n";
	printf "Note: either -k or -d or -T must be specified and -d and -T are much more network \n";
	printf "intensive.  Use it sparingly or not at all.  -n is used to match against\n";
	printf "a much more descriptive ifName value in the IfXTable to verify that the\n";
	printf "snmpkey has not changed to some other network interface after a reboot.\n\n";
	
}

sub process_arguments() {
	$status = GetOptions(
			"V"   => \$opt_V, "version"    => \$opt_V,
			"h"   => \$opt_h, "help"       => \$opt_h,
			"v=i" => \$snmp_version, "snmp_version=i"  => \$snmp_version,
			"C=s" => \$community, "community=s" => \$community,
			"L=s" => \$seclevel, "seclevel=s" => \$seclevel,
			"a=s" => \$authproto, "authproto=s" => \$authproto,
			"U=s" => \$secname,   "secname=s"   => \$secname,
			"A=s" => \$authpass,  "authpass=s"  => \$authpass,
			"X=s" => \$privpass,  "privpass=s"  => \$privpass,
			"P=s" => \$privproto,  "privproto=s"  => \$privproto,
			"c=s" => \$context,   "context=s"   => \$context,
			"k=i" => \$snmpkey, "key=i",\$snmpkey,
			"d=s" => \$ifdescr, "descr=s" => \$ifdescr,
			"l=s" => \$lastc,  "lastchange=s" => \$lastc,
			"p=i" => \$port,  "port=i" =>\$port,
			"H=s" => \$hostname, "hostname=s" => \$hostname,
			"I"   => \$ifXTable, "ifmib" => \$ifXTable,
			"n=s" => \$ifName, "name=s" => \$ifName,
			"w=s" => \$dormantWarn, "warn=s" => \$dormantWarn,
			"D=s" => \$adminWarn, "admin-down=s" => \$adminWarn,
			"M=i" => \$maxmsgsize, "maxmsgsize=i" => \$maxmsgsize,
			"t=i" => \$timeout,    "timeout=i" => \$timeout,
			"T=i" => \$iftype,    "type=i" => \$iftype,
			);


	if ($status == 0){
		print_help();
		exit $ERRORS{'OK'};
	}

	if ($opt_V) {
		print_revision($PROGNAME,'');
		exit $ERRORS{'OK'};
	}

	if ($opt_h) {
		print_help();
		exit $ERRORS{'OK'};
	}

	if (! utils::is_hostname($hostname)){
		usage("Hostname invalid or not given");
	}

	unless ($snmpkey > 0 || defined $ifdescr || defined $iftype){
		usage("Either a valid snmp key (-k) or a ifDescr (-d) must be provided");
	}

	if (defined $ifName) {
		$ifXTable=1;
	}	

	if (defined $dormantWarn) {
		unless ($dormantWarn =~ /^(w|c|i)$/ ) {
			printf "Dormant alerts must be one of w|c|i \n";
			exit $ERRORS{'UNKNOWN'};
		}
	}
	
	unless (defined $timeout) {
		$timeout = $TIMEOUT;
	}

	if ($snmp_version !~ /[123]/){
		$state='UNKNOWN';
		print ("$state: No support for SNMP v$snmp_version yet\n");
		exit $ERRORS{$state};
	}

	%session_opts = (
		-hostname   => $hostname,
		-port       => $port,
		-version    => $snmp_version,
		-maxmsgsize => $maxmsgsize
	);

	$session_opts{'-community'} = $community if (defined $community && $snmp_version =~ /[12]/);

	if ($snmp_version =~ /3/ ) {
		# Must define a security level even though default is noAuthNoPriv
		# v3 requires a security username
		if (defined $seclevel && defined $secname) {
			$session_opts{'-username'} = $secname;
		
			# Must define a security level even though defualt is noAuthNoPriv
			unless ( grep /^$seclevel$/, qw(noAuthNoPriv authNoPriv authPriv) ) {
				usage("Must define a valid security level even though default is noAuthNoPriv");
			}
			
			# Authentication wanted
			if ( $seclevel eq 'authNoPriv' || $seclevel eq 'authPriv' ) {
				if (defined $authproto && $authproto ne 'MD5' && $authproto ne 'SHA1') {
					usage("Auth protocol can be either MD5 or SHA1");
				}
				$session_opts{'-authprotocol'} = $authproto if(defined $authproto);

				if ( !defined $authpass) {
					usage("Auth password/key is not defined");
				}else{
					if ($authpass =~ /^0x/ ) {
						$session_opts{'-authkey'} = $authpass ;
					}else{
						$session_opts{'-authpassword'} = $authpass ;
					}
				}
			}
			
			# Privacy (DES encryption) wanted
			if ($seclevel eq 'authPriv' ) {
				if (! defined $privpass) {
					usage("Privacy passphrase/key is not defined");
				}else{
					if ($privpass =~ /^0x/){
						$session_opts{'-privkey'} = $privpass;
					}else{
						$session_opts{'-privpassword'} = $privpass;
					}
				}

				$session_opts{'-privprotocol'} = $privproto if(defined $privproto);
			}

			# Context name defined or default
			unless ( defined $context) {
				$context = "";
			}
		
		}else {
			usage("Security level or name is not defined");
		}
	} # end snmpv3

}
## End validation


Скрипт не мой, но я не помню, что я тут менял, чтобы оно заработало с именем интерфейса. Поэтому вот он целиком.

Ну и на этом пока всё про nagios и его плагины.
Поделиться с друзьями
-->

Комментарии (9)


  1. AcidVenom
    17.08.2016 16:16
    +1

    Ветка Synology — .1.3.6.1.4.1.6574. И есть Гайд, а в нем ссылка на MIB'ы.
    P.S.: Вы как раз ее и используете.


    1. Smithson
      17.08.2016 16:45

      Вы правы, забыл указать: OID Synolog 1.3.6.1.4.1.6574. Статью править уже не буду, пусть будет тут.


  1. deimond
    18.08.2016 06:10
    +1

    Для cisco в зависимости от версии ios «snmp-server ifindex persist» или «snmp ifmib ifindex persist» предотвратит смену ifIndex при каждом ребуте.


    1. Smithson
      18.08.2016 09:50

      Спасибо, не знал. Но согласитесь, что перезагрузка cisco событие не каждого года и даже не каждой пятилетки %) Поэтому редко кто задумывается, что там будет, после потопа.


      1. deimond
        18.08.2016 10:27
        +1

        Но скрипт в over тысячу строк-то вы запилили, чтоб решить проблему сброса индексов при перезагрузке, которая случается «даже не каждую пятилетку» :) Кстати если появится необходимость мониторить QoS, пригодится еще одна команда из той же серии: «snmp mib persist cbqos».


        1. Smithson
          18.08.2016 10:28

          Это не я, я там буквально две строчки правил :)


  1. Smithson
    18.08.2016 09:54

    Забыл указать про vmware. Начиная с какого-то опять же билда vmware перестала отдавать по snmp загрузку процессоров. То есть mib такой есть, на запрос отвечает, но данные там лажовые и постоянные.

    Did not figure out how to use SNMP, but I got vSphere Perl SDK downloaded form vmware, and used CLI apps to access vCenter API to get CPU load numbers that match those reported in vCenter.


    1. Speccyfan
      21.08.2016 20:54
      +1

      Мониторить VMware по SNMP, не самая лучшая идея, у нее есть вполне юзабельнное SDK, его использует vpoller например, довольно мощная штука. Я его использую для мониторинга свободного места на датасторах и нагрузку на хосты.


      1. Smithson
        22.08.2016 15:02

        Из моего опыта, vmware snmp-ответы кеширует. Если часто опрашивать хост (в режиме отладки, например), то видно, что значения меняются с шагом 2-3 минуты, не быстрее. В пределах этих 2-3 минут возвращается один и тот же результат.
        То есть нагрузка от snmp на гипервизор ограничена таким способом.

        На vpoller посмотрю, спасибо.