null

Задержка отправки SMS в Zabbix

В системе отправки SMS у Zabbix есть два недочёта. Первый относится ко всем типам уведомлений. Это невозможность задания "периода тишины" без потери уведомлений. Второй -- кодировка отправляемой SMS.
Мой коллега достаточно детально разобрал то, как Zabbix работает с GSM модемом. Как оказалось, никакого кодирования он не производит, пишет отправляемые символы напрямую. Используется семибитная кодировка, а по хорошему надо использовать восьмибитную или шестнадцатибитную UCS-2.
Поэтому в приходящих сообщениях иногда возникают трудности с кодировкой.
Традиционным способом обхода проблем в подсистеме отправки SMS у Zabbix было написание внешних Alert-скриптов, поэтому и я предлагаю очередной велосипед.
К требованиям относятся:

  • поддержка работы с gammu (бывший gnokii);
  • поддержка "периода тишины" -- когда сообщения не отправляются, а только ставятся в очередь;
  • работа с Zabbix 3.0.

Рассмотрим предлагаемый скрипт:

#!/usr/local/bin/perl
# made by: KorG
# USAGE: $0 [0] [12] <user> <cmd>

use strict;
use v5.18;
use utf8;
use warnings;
no warnings 'experimental';

use Storable;
use IPC::SysV qw(IPC_CREAT SEM_UNDO IPC_EXCL);
use IPC::Semaphore;

# user defined variables
my $db = "/home/korg/sms.db";
my $key = 20160626;

# synchronize
my $sem = IPC::Semaphore->new($key, 1, 0700 | IPC_CREAT | IPC_EXCL) ||
  IPC::Semaphore->new($key, 1, 0600) || die "semaphore: $!";
$sem->setall(1);
$sem->op(0, -1, SEM_UNDO);

# DB operations
store {}, $db unless -r $db;
my %db = %{retrieve($db)};

# atexit operations
END {
  for (keys %db) {
    delete $db{$_} unless exists $db{$_}->{time}->{time_start};
  }
  store \%db, $db or warn "store: $!";
  $sem->remove;
}

# actual commands execution
sub execute_user_cmds($) {
  my $user = shift // return;

  my $aggregation = "";
  while ($_ = shift @{ $db{$user}->{commands} }) {
    $aggregation .= "@{$_}\n";
  }

  # gammu specific part
  open my $gammu, "| /usr/local/bin/gammu sendsms TEXT '$user' -autolen 160" or 
    die "run gammu: $!";
  print $gammu $aggregation;
}

# execute dispatcher
sub run_user_queue($) {
  my $user  = shift // return;

  my $time  = (localtime)[2];
  my $start = $db{$user}->{time}->{time_start};
  my $end   = $db{$user}->{time}->{time_end};

  unless (defined $start && defined $end) {
    return execute_user_cmds $user;
  }

  return if (
    ( $start > $end && ($time >= $start || $time < $end) ) ||
    ( $start <= $end && ($time >= $start && $time < $end) )
  );

  return execute_user_cmds $user;
}

# parse arguments
given ($#ARGV) {
  when (-1) {
    run_user_queue $_ for keys %db;
  }
  when (0) {
    run_user_queue $ARGV[0];
  }
  default {
    my $user = shift;
    my ($t_start, $t_end) = (undef, undef);

    if ($user =~ /^\d\d?$/) {
      $t_start = $&;

      shift =~ /^\d\d?$/;
      $t_end = $&;

      $user = shift;
    }

    # delay execution
    push @{ $db{$user}->{commands} }, [@ARGV];
    $db{$user}->{time}->{time_start} = $t_start if defined $t_start;
    $db{$user}->{time}->{time_end} = $t_end if defined $t_end;

    # force commands dispatch
    run_user_queue $user;
  }
}

Видно, что он идеально подходит для более глобальной цели -- планирование времени выполнения команд так, чтобы в заданный промежуток команды не выполнялись, а ставились в очередь.
Чтобы переделать скрипт для работы с любыми командами достаточно поменять этот участок кода:

sub execute_user_cmds($) {
  my $user = shift // return;

  system @{ $_ } while ($_ = shift @{ $db{$user}->{commands} });
}

Синтаксис вызова скрипта следующий:

sms.pl [ prohibited_hours_from prohibited_hour_till ] [[ user ] | [ user message ]]

При запуске без аргументов скрипт просто проверяет очереди команд и выполняет те команды, которые можно выполнить.
При запуске с одним аргументом -- user -- проверяется только очередь для конкретного пользователя.
При запуске с тремя или более аргументами, первые два из которых числа, эти числа интерпретируются как часы начала и завершения периода тишины, следующий за ними аргумент -- имя пользователя и все остальные -- текст сообщения. Если первые два аргумента не числа, первый считается именем пользователя, а остальные -- текстом.
При этом, время начала и завершения периода тишины включает час начала и не включает час завершения.
Например, 3-8 означает диапазон с 3:00:00 до 7:59:59.
Важно то, что если указан временной промежуток, он автоматически сохраняется в файловой базе данных и используется в дальнейшем.

Текущее содержимое базы, очевидно, можно посмотреть простой командой:

perl -MStorable -MData::Dumper -e 'print Dumper(retrieve "sms.db")'

Для обеспечения периодичности можно добавить вот такую строку в crontab:

1 * * * * /usr/local/bin/sms.pl

Кроме того, необходимо настроить пути к файлам:

# необходимо задать путь к базе данных
my $db = "/home/korg/sms.db";
# необходимо задать путь к gammu
open my $gammu, "| /usr/local/bin/gammu ..." or die "run gammu: $!";

Для настройки уведомлений в Zabbix 3.0 нужно расположить скрипт в переменной, указанной в AlertScriptsPath, создать свой Media type (в Administration), в котором указать имя скрипта и передаваемые параметры. Рекомендуется в качестве последних указывать "{ALERT.SENDTO}", "{ALERT.SUBJECT}" и "{ALERT.MESSAGE}".
Настройка типа уведомлений завершена. Осталось лишь добавить нужным пользователям привязку к этому типу (в свойствах пользователей) и уведомления начнут приходить.
На этом всё, теперь ночные SMS будут приходить в 8 утра.

P.S. к слову сказать, если вас напрягает popen или экранирование номера телефона в shell, можно обойтись и без него:

system("/usr/bin/gammu", "sendsms", "TEXT", "$user", "-autolen", "160", "-text", "$aggregation");

 

korg

 

Коротко о себе

Работаю в компании Tune-IT, администрирую инфраструктуру компании и вычислительную сеть кафедры Вычислительной ТехникиСПбНИУ ИТМО.

Интересы: администрирование UNIX и UNIX-like систем и активного сетевого оборудования, написание shell- и perl-скриптов, изучение технологий глобальных сетей.
Люблю собирать GNU/Linux и FreeBSD, использовать тайлинговые оконные менеджеры и писать системный софт.