Собираем конфигурацию свичей

 

Суть такова: есть количество управляемых свичей, нужно с них собирать конфигурационные файлы.

Берем TFTP сервер, Perl и два модуля (Net::Telnet & File::Copy).

Так же есть файлик hosts.txt

Его вид такой:

12.23.45.54 dlink
24.21.23.54 dlink_simple

Суть такова: айпи адрес и... "тип устройства" =)
Файлик обрабатывается подпрограммой parse();

В этой подпрограмме указывается какое содержимое переменной $cmd нужно сказать свичу что бы он отдал конфиг на TFTP.

А! Есть еще момент. В старой и доброй традиции юниксового TFTPD нужно что бы файлик, который мы передаем на сервер, уже был на этом сервере.

Т.е. подпрограмма tftp_create() как раз и создает такой файлик нулевого размера.

  
#!/usr/bin/perl
##########################################
##
## ololo
## (C) Ilya Vasilyev, 2010
## nadz.goldman@gmail.com
##
## http://arviol.ru/
##
##
##########################################
##########################################
use Net::Telnet;
use File::Copy;
my ( $cmd; $host; $type; $conn; )
# айпи тфтп сервера
$tftp_ip = "12.2.2.3";
# логин+пароль свича
$username = "bak";
$passwd = "bak";
# директория тфтп-сервера, в которую кидаются/из которой берутся файлы, прилетевшие на тфтп
$tftp_path = "/tftp";
# рабочая директория скрипта $main_path = "/home/bak/ololo";
# так у меня выглядит дата
$date = `/bin/date "+%Y-%m-%d"`;
# это для винды
#$date = `date /T`;
# так выглядит имя файла конфигурации, прилетевшего со свича
$filename = $host."-".$date; ####################################################################################
# вызов подпрограммы для распарсивания файла с хостами
parse();
# выполнение бэкапа
execute();
# перенос в директорию бэкапа
mov_bak();
# подпрограмма переноса
sub mov_bak(){
# обрезаем фигню всякую
chomp( $date );
# создаем директорию на текущую дату для бэкапа
system( "mkdir -p ".$main_path."/bak/".$date );
# двигаем туда прилетевшие к нам конфиги
system( "mv ".$tftp_path."/*_".$date." ".$main_path."/bak/".$date );
# скидываем в файл список пустых файлов - это те свичи,
# до которых мы либо не достучались, либо с них ничего не прилетело
system( "find ".$main_path."/bak/ -empty -exec ls {} \\; > ".$main_path."/NOT_BACKUP.txt " );
}
sub put_conf(){
# на будущее =))
}
# подпрограмма соединения со свичом по телнету
sub connect_(){
my $temp;
$temp = length( $host );
# если вдруг переменная $host оказалась пуста, значит какой-то ахтунг произошел и скрипт умирает
if( $temp == 0 ){
die "No ip address ( func::connect_ ) !\n Exiting...\n";
}
# опции соединения
$conn = new Net::Telnet( Timeout => 5 , Errmode => 'return' , Dump_Log => 'DUMP.LOG' );
$conn->open( Host=>$host );
# допиливаю вывод ошибок
$msg = $conn -> errmsg;
$prev = $conn -> errmsg( @msgs );
foreach $prev( @msgs ){ print "$prev\n"; }
# создание дампов
$dump = $conn -> dump_log;
$dump = $conn -> dump_log( $dump );
$dump = $conn -> dump_log( $main_path.'/dump/DUMP.LOG' );
# ожидаем строки UserName/PassWord и дождавшись, скидываем туда логин и пароль
# Можно и вот так: $conn -> waitfor( '/username[: ]*$/i' ); , но эт потом...
# Просто потому что некоторые свичи пишут login или сразу password
$conn -> waitfor( '/ame[: ]*$/' );
$conn -> print( $username );
$conn -> waitfor( '/ord[: ]*$/' );
$conn -> print( $passwd );
}
# дисконнектимся
sub disconnect(){
$conn -> print( "logout" );
$conn -> close;
undef( $conn );
copy( $main_path.'/dumps/DUMP.LOG', $main_path.'/dumps/DUMP.LOG-'.$host );
}
# собственно здесь мы и забираем конфиг
sub get_conf(){
my $temp;
$temp = length( $host );
# если вдруг переменная $host оказалась пуста, значит какой-то ахтунг произошел и скрипт умирает
if( $temp == 0 ){ die "No ip address ( func::get_conf ) !\n Exiting...\n"; }
# создаем файлик в директории тфтп сервера
tftp_create();
# говорим свичу команду на отправку конфига
$conn -> print( $cmd );
# ждем - иногда свичи тупят, если сразу отвалится, то глупости будут
sleep( 15 );
}
# подпрограмма создания файла в каталоге тфтп
sub tftp_create{
my $temp;
$temp = length( $host );
# если вдруг переменная $host оказалась пуста, значит какой-то ахтунг произошел и скрипт умирает
if( $temp == 0 ){ die "No ip address ( func::tftp_create ) !\n Exiting...\n"; }
# создаем файл
system( "touch $tftp_path"."/"."$filename" );
# устанавливаем права
system( "chmod 0666 $tftp_path"."/"."$filename" );
# ждем...
sleep( 2 );
}
# подпрограмма выполнения
sub execute {
my $i;
# ищем файлик со свичами цисок
if( -f $main_path."/cisco.txt" ){
open( cs , "< ".$main_path."/cisco.txt" ) or print "While executing, can not open cisco.txt";
while( defined( $i = )) { print( "Yeeehaaa!!! It is CISCO!! =)) \n\n" ); }
close( cs );
}
# ищем файлик с длинками
if( -f $main_path."/dlink.txt" ){
open( dl , "< ".$main_path."/dlink.txt" ) or print "While executing, can not open dlink.txt";
while( defined( $i = )){
chomp( $i );
$host = $i;
$filename = $host."_".$date;
# ВОТ эта команда будет сказана свичю после соедиенения и атворизации
$cmd = "upload cfg_toTFTP $tftp_ip $filename";
# для дебага =)
#print( "Host: $host\nfile: $filename cmd: $cmd" );
# соеденились
connect_();
# забрали
get_conf();
# ушли
disconnect();
}
close( dl ); }
# А! Вот эта штука... Потому что длинки разные, то и набор команд у них разный.
# сейчас как раз пилю что бы этой ереси с кучей файлов не было
if( -f $main_path."/dlink_simple.txt" ){
open( dl_simple , "< ".$main_path."/dlink_simple.txt" ) or print "While executing, can not open dlink_simple.txt";
# upload configuration
while( defined( $i = )) {
chomp( $i );
$host = $i;
$filename = $host."_".$date;
$cmd = "upload configuration $tftp_ip $filename";
#print( "Host: $host\nfile: $filename cmd: $cmd" );
connect_();
get_conf();
disconnect();
}
close( dl_simple );
}
return();
}

# подпрограмма парсинга
sub parse {
 my $i;
 open( cs , "+> ".$main_path."/cisco.txt" ) or print( "Can not open cisco.txt\n" );
 open( dl , "+> ".$main_path."/dlink.txt" ) or print( "Can not open dlink.txt\n" );
 open( dl_simple , "+> ".$main_path."/dlink_simple.txt" ) or print( "Can not open dlink_simple.txt\n" );
open( err_txt , "+> ".$main_path."/error.txt" ) or print( "Can not open error.txt\n" );
open( fh , "< ".$main_path."/hosts.txt" ) or die "Can not open hosts.txt!\n";
flock( fh , 2 ) or die "Can not flock hosts.txt!\n";
while( defined( $i = )) {
( $host, $type ) = split( / /, $i );
if( $type =~ /cisco/i ) {
#print "host = $host ... type = $type";
print cs "$host\n";
} elsif (
 $type =~ /^dlink$/i ) {
#print "host = $host ... type = $type";
print dl "$host\n";
} elsif (
$type =~ /^dlink_simple$/i ) { print dl_simple "$host\n"; }
else { print err_txt "$host $type\n"; }
}
close( fh );
close( cs );
close( dl );
close( err_txt );
close( dl_simple );
return();
}

Ясно и понятно, что всё это криво, косо и неоптимизировано. Но таки работает.

Сейчас уже всё достаточно сильно изменилось, т.е. это старый код.  Свежий надо допилить до логического завершения.

Так же приложу кусочек кода из нового релиза.  Это позволяет вносить конфигурации в базу данных MySQL.

  
#!/usr/bin/perl
use DBI;
my $date = `/bin/date "+%Y-%m-%d"`;
my $main_path = "/home/ilya/ololo";
my $path = $main_path."/bak/".$date;
my $cur_file;
my $i;
my $f;
my @fcont;
my $dbh;
my $dbhost = "localhost";
my $dbuser = "dbuser";
my $dbpass = "dbpass";
my $dbname = "dbname";
my $dbport = "3306";
sub insert_data(){
 chomp( $main_path );
 chomp( $path );
 chdir( $path );
 $dbh = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost" , $dbuser , $dbpass , {'RaiseError' => 1});
 opendir ( DIR , "$path" );
 @files = grep( !/^\.+$/,readdir( DIR ) );
 foreach $cur_file ( @files ){ open( fh , $cur_file ) || die "Error: $_";
 @fcont = ;
 my $blob = $dbh->quote( join('', @fcont) );
 $dbh->do( "INSERT INTO conf ( my_date , conf , ip ) VALUES ( ".$dbh->quote( $date )." , ".$blob." , ". $dbh->quote( $cur_file )." );" );
 close( fh );
 }
closedir( DIR );
$dbh->disconnect();
}

 arviol.ru, 2006

Докер -- Сильно. Выгодно. Надежно