Perl для системного администрирования

       

Контролируем количество сообщений



Контролируем количество сообщений

Другая разновидность синдрома «чрезмерной отправки почты» - это проблема «каждый в сети за себя». Если все машины из сети решат послать вам чуточку почты, вы вполне можете пропустить что-то действительно важное в этом потоке сообщений. Было бы лучше, если бы все сообщения отправлялись в центральный репозиторий. А затем в собранном виде почта поступала бы в одном сообщении.

Давайте рассмотрим несколько надуманный пример. Предположим, что каждая машина в сети записывает в разделяемый каталог файл, состоящий из одной строки. Имя каждого файла совпадает с именем машины и в каждом из них хранятся результаты научных вычислений, сделанных прошлой ночью. В файле будет одна строка следующего формата:

имя-узла

удача-или-неудача

количество-завершенных-вычислений

Программа, проверяющая эту информацию и отсылающая результаты, может выглядеть так:

use Mail::Mailer; use Text::Wrap;

tf список машин, отправляющих сообщения

$repolist = "/project/machinelist";

ft каталог, куда они записывают файлы

Srepodir = "/project/reportddir";

it

разделитель файловой системы, используется для переносимости.

Можно было бы использовать модуль File::Spec

$separator= "/";



# отправляем почту "с" этого адреса

$reportfromaddr = "project\@example. corn";

# отправляем почту на этот адрес Sreporttoaddr = "project\@example.com";

tf считываем список машин в хэш. Позже будем вынимать из этого

# хэша по мере доклада машин, оставив только те машины, которые

не принимали участие в действии

open(LIST,$repolist) or die "Невозможно открыть список

$repolist:$!\n"; while(<LIST>){

chomp;

$missing{$_}=1;

$machines++; }

# считываем все файлы из центрального каталога

и замечание: этот каталог должен автоматически очищаться другим

# сценарием

opendir(REPO,Srepodir) or die "Невозможно открыть каталог $repodir:$!\n";

while(defined($statfile=readdir(REPO))){

next unless -f Srepodir.$separator.$statfile;

# открываем каждый файл и считываем информацию о состоянии

open(STAT,$repodir.$separator.$statfile) or die "Невозможно открыть Sstatfile:$!\n";

chornp($report = <STAT>);

($hostname.$result,$details)=spiit(' ',$report,3);

warn "В файле Sstatfile утверждается, что он был сгенерирован машиной

Shostname1 \n" if($hostname ne Sstatfile);

имя узла больше не считается пропущенным

delete $missing{$nostname}; # заполняем значениями хэши

(Sresult eq "success")}

$success{$nostname}=$details;

$succeeded++: } else {

$fail{$hostname}=$details:

$failed++; }

close(STAT); } closedir(REPO);

# создаем информативный заголовок для сообщения if (Ssucceeded == $machines){

Ssubject = "[report] Success: $machines"; }

elsif ($failed == Smachines or scalar keys %missing >= Smachines) {

Ssubject = "[report] Fail: Smachines"; } else

{ ;

Ssubject = "[report] Partial: Ssucceeded ACK, Sfailed NACK".

((%missing) ? ", ".(scalar keys %missing)1." MIA" : ""); }

# создаем объект mailer и заполняем заголовки $type="sendmail";

my Smaller = Mail::Mailer->new($type) or die "Невозможно создать новый объект:$!\п";

$mailer->open({From=>$reportf romaddr,

To=>$reporttoaddr. Subject=>$subject})! or die "Невозможно заполнить объект mailer:$!\n";

» создаем тело сообщения !

print $mailer "Run report from $0 on " . scalar localtime(tine) . "\n":

if (keys %success){

print Smaller "\n==Succeeded==\n";

foreach $hostname (sort keys %success){

print Smaller "$hostname: $success{$hostname}\n":

} } 308

if (keys %fail){

print Smaller "\n==Failed==\n";

foreach Snostname (sort, keys %fail){

print Smaller "Shostname: $fail{$hostname}\n"

} }

if (keys %missing){

print Smaller "\n==Missing==\n";

print Smaller wrap("","".join(" ".sort keys %missing)),"\n"; }

# отправляем сообщение $mailer->close;

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

Получается такой отчет:

Date: Wed, 14 Apr 1999 13:06:09 -0400 (EOT)

Message-Id: <199904141706.NAA08780@example.com> Subject: [report]

Partial: 3 ACK, 4 NACK, 1 MIA To: project(s>example. con From: project@example.com

Run report from reportscript on Wed Apr 14 13:06:08 1999

==Succeeded==

barney: computed 23123 oogatrons betty: computed 6745634

oogatrons fred: computed 56344 oogatrons

==Failed==

bambam: computed 0 oogatrons dino: computed 0

oogatrons pebbles: computed 0

oogatrons wilma: computed 0 oogatrons

==Missing== mrslate

Другой способ изучить подобные результаты состоит в том, чтобы создать демон журналов регистрации и посылать отчет от каждой машины через сокет. Сначала взгляните на код для сервера. Он совпадает с кодом из предыдущего примера. Рассмотрим новую программу и обсудим ее важные особенности:

use 10::Socket;

use Text::Wrap: ft используется для создания аккуратного вывода

список машин, посылающих отчеты Srepolist = "/project/machinelist":

номер порта для соединения с клиентами Sserverport = '9967' ;

Sloadmachines: # загружаем список ма^ин

# настраиваем нашу сторону сокета

Sreserver = 10::Socket::INET->new(LocalPort => Sserverport,

Proto => "tcp",

Type => SOCK_STREAM,

Listen => 5,

Reuse => 1) or die "Невозможно настроить сокет на нашей стороне: $!\п";

и начинаем слушать порт в ожидании соединений while(

($connectsock,Sconnectaddr) = $reserver->accept()){

# имя подсоединившегося клиента

Sconnectname = gethostbyaddr((sockaddr_in($connectadar))[1],AF_INЈT):

chomp($report=$connectsock->getline);

($hostname,$result,$details)=split(' ',$report,3);

в если нужно сбросить информацию, выводим готовое к

# отправке сообщение и заново инициализируем все

хэши/счетчики

if (Shostname eq "DUMPNOW'H

&printmail($connectsock);

close($connectsock):

undef %success;

undef %fail:

Ssucceeded = Sfailed = 0:

&loadmachines;

next: }

warn "$connectname говорит, что был сгенерирован $nostnarce' \r-."

if($hostname ne Sconnectnaiie): delete $n;issiP.g{Shostna"rie}:

($result eq "success")!

$success{Shostnare}=$deraiIs:

$succeeded++: / else 1

$fail{$hostrame}=$dera;ls:

$fai!ed++. }

close($connectsock); } close($reserver):

# загружаем список машин из заданного файла sub loadmachines

undef %missing;

undef Smachines;

open(LIST,$repolist) or die "Невозможно открыть список Srepolist:$!\n";

while(<LIST>){

chomp;

$missing{$_}=1;

$machines++; } }

выводим готовое к отправке сообщение. Первая строка - тема,

# последующие строки - тело сообщения sub printmail<

(Ssocket) = $_[0];

if (Ssucceded == $machines){

Ssubject = "[report] Success: Smachines"; }

elsif ($failed == Smachines or scalar keys %missing >= Smachines) {

Ssubject = "[report] Fail: Smachines"; } else {

Ssubject = "[report] Partial: Ssucceeded ACK, Sfailed NACK".

((%missing) ? ", ".(scalar keys %missing)1." MIA" : ""); }

print Ssocket "$subject\n":

print Ssocket "Run report from $0 on ".scalar localtime(time)."\n";

if (keys %success){

print Ssocket "\n==Succeeded==\n";

foreach Shostname (sort keys %success){ print

Ssocket "Shostname: $success{$hostname}\n":

}

}

if (keys %fail){ print Ssocket "\n==Failed==\n":

foreach Shostname (sort keys %fail)< print $socket "Shostname: $fail{$hostname}\n"; } }

if (keys %nissing){ print Ssocket " \n==Missing==\n":

print $socket wrapC"1."" join(" ".sort keys Vrissi^g) ).'"

}

Кроме переноса части кода в отдельные подпрограммы, главное изменение заключается в том, что добавлен код для работы с сетью. Модуль 10: : Socket позволяет без труда открывать и использовать сокеты, которые можно сравнить с телефоном. Сначала нужно установить свою сторону сокета (10: :Socket->new()), как бы включая свой телефон, а затем ждать «звонка» от клиента (10: :Socket»accept()). Программа приостановлена (или «заблокирована») до тех пор, пока не установлено соединение. Когда соединение установлено, запоминается имя подсоединившегося клиента. Затем из сокета считывается строка ввода.

Мы предполагаем, что строка ввода выглядит точно так же, как и строки из отдельных файлов в предыдущем примере. Единственное различие - это загадочное имя узла DUMPNOW. Если это имя встречается, подсоединившемуся клиенту выводится тема и тело готового к отправке сообщения, при этом сбрасываются все счетчики и хэш-таб-лицы. За отправку сообщения, полученного от сервера, ответственен клиент. Теперь посмотрим на пример клиента и узнаем, что он может сделать с этим сообщением:

use 10::Socket;

номер порта для соединения с клиентом

Sserverport = "9967";

и имя сервера

$servername = "reportserver";

ft преобразуем имя в IP-адрес

Sserveraddr = inet_ntoa(scalar gethostbyname($servername));

Sreporttoaddr = "project\@example.com";

Sreportf romaddr = "project\(g>example.com";

Sreserver = 10::Socket::INET->new(PeerAddr => Jserveraddr.

PeerPort => $serverport Proto => "tcp". Type => SCCK^STREAM) or ale

"Невозможно создать сокет -а нашей стороне:: $!\п":

if ($ARGV;;0] re "-ni"){

print Sreserver $ARGV[0]: v else {

use Mail::Mailer;

print Sreserver "DUMPNOW\T;

chomp($subject = <$reserver.>) $body = join("",<$reserver>);

$type="send!rmil";

my Smaller = Mail::Mailer->new($type) or die

"Невозможно создать новый обьект niailer-$' \n";

$mailer->open({

From => $reportfromaddr To => $reporttoaddr, Subject => Ssubject » or die

"Невозможно заполнить объект mailer:$!\n";

print Smaller $body; $mailer->close; }

close($reserver);

Эта программа проще. Сначала открывается сокет с сервером. В большинстве случаев ему передается информация о состоянии (полученная в командной строке как $ARGV[0]) и соединение закрывается. При желании создать клиент-серверную систему регистрации, подобную этой, вероятно, нам пришлось бы перенести данный клиентский код в подпрограмму и вызывать ее из другой, гораздо более крупной.

Если передать сценарию ключ -т, он отправит серверу «DUMPNOW» и прочитает полученную от него строку темы сообщения и тело сообщения. Затем этот вывод передается модулю Mail: : Mailer и отправляется в виде почтового сообщения при помощи той же программы, которую мы видели раньше.

Для ограничения размера примера и для того, чтобы не уходить в сторону от дискуссии, здесь представлен лишь костяк кода для клиента и сервера. В нем нет ни проверки ошибок или ввода, ни управления доступом, ни авторизации (в сети любой, получивший доступ к серверу, может взять с него данные), ни постоянного хранилища данных (а что, если машина не работает?), ни даже мало-мальских мер предосторожности. Мало того, в каждый момент времени можно обрабатывать только один запрос. Если клиент остановится в середине транзакции, мы «влипли». Более изощренные примеры можно найти в книгах «Advanced Perl Programming» (Углубленное программирование на Perl) Шрирама Шринивасана (Sriram Srinivasan) и «Perl Cookbook» («Perl: Библиотека программиста») Тома Кристиансена (Tom Christiansen) и Натана Торкингтона (Nathan Torkington), обе выпущены издательством O'Reilly. Модуль Net:: Daemon Джошена Вьедмана (Jochen Wi-edmann) также поможет создавать более сложные программы-демоны.

Однако пора вернуться к рассмотрению других ошибок, допускаемых в программах для системного администрирования, отправляющих почтовые сообщения.



Содержание раздела