Gathering network equipment configuration and storing them in SVN

Not so long ago, due to the expansion of the staff of network administrators, the increase in the number of equipment in the network and the range of tasks assigned to this equipment, there was a desire to track the change in its configuration and keep a history of these changes.
The obvious solution was to store the configuration in the version control system. The choice fell on Subversion, also known as SVN.
I will not dwell on installing and configuring the repository, since many pages on the Internet are devoted to this and there is nothing non-standard in this. Here I will describe my method of collecting configurations from equipment and accounting for changes.

To begin with, we will create a working copy of our repository, where the configs will be collected on the perl script. Let's start writing the script. The script was written taking into account several features.
svn checkout svn://svn.reposerver.ru/configs /configs


  • simple add / remove devices in the list of scanned
  • multivendor network
  • notification of changes by mail
  • mail connection error notification

Initialization of variables:
#!/usr/bin/perl -w 
use Net::Appliance::Session;
use Net::SCP::Expect;
use Switch;
use Fcntl ':flock'; 
exit(1) unless &lock("lockfile"); 
my $date = `/bin/date`;
my $flag=0; ### флаг наличия изменений
my $path="/configs"; ###путь к рабочей копии
my $cfg = "/configs/switches.cfg"; ### файл списка оборудования
my $error = "\n"; ### переменная сообщений об ошибке.

We make an update and get the current revision number. It is useful to us for comparing versions.
$_=`/usr/local/bin/svn update $path`;
s/At revision (\d+)./$1/;
$prev_rev = $_;

Lock / unlock functions. Allow you to run only one copy of the script.
sub lock($) {
	open(LK,">$_[0]") or return 0;
	flock(LK,LOCK_EX|LOCK_NB);
}
sub unlock($) {
        unlink($_[0]);
	flock(LK,LOCK_UN);
	close(LK);
} 

Next, read the configuration file. The file should be a set of lines. Each line contains the address of the piece of iron and its type, separated by a colon:
switch1:e
switch2:cat
router1:c
router2:j

open (CFG, "$cfg") || die "Cant open file $cfg: $!";
while () {
	next if /^\#/; ###Пропускаем комментарии в конфиге
        next if /^\s*$/; ###Пропускаем пустые строки в конфиге
	chomp;
	my @host = split /:/,$_;
	switch($host[1]) {
		case "cat" {
			`/usr/bin/touch $path/$host[0]`;
			&cat_telnet($host[0]);
			&str_remove ($host[0]);
		}
		case "c" {
			`/usr/bin/touch $path/$host[0]`;
			&cat_telnet($host[0]);
			&str_remove ($host[0]);
		}
		case "e" {
			 `/usr/bin/touch $path/$host[0]`;
		    	&extreme_telnet($host[0]);
		    	&str_remove ($host[0]);
		}
		case "j" {
		    &juni_scp($host[0]);
		}
	}

Here, for each line of the config, we create a configuration file (in case it wasn’t) and call a set of functions depending on the type of device. We will consider the functions later, I will only say that after their execution we will have a new config assembled from the device. Check if there are any changes in the config. If there is, commit and set the flag for changes to 1. If a new host is added, we also add it to the repository. With the flag set, write a letter.
my $svnst = `/usr/local/bin/svn status $path/$host[0]`;
    	if ($svnst =~ /^M/) { ### Если файл был модифицирован
		$flag=1;
		`/usr/local/bin/svn commit -m "$date" $path/$host[0]`;	
	}
if ($svnst =~/^\?/) { ### Если добавился новый файл
		`/usr/local/bin/svn add $path/$host[0]`;
		`/usr/local/bin/svn commit -m "Add new host at $date" $path/$host[0]`;
	}
}
close CFG;
if ($flag==1) {
	&diff_mail();
}

The main script is finished. Further set of functions.
Due to the multi-vendor nature of the config collection function, it was decided to use a more or less universal mechanism - a connection to a switch and a command to view the configuration.
sub cat_telnet {
	(my $host1)=@_;
	$try = 5;
	$update='false';
	while ($try>0) {
		my $s = Net::Appliance::Session->new(
			Host => $host1,
			Transport => 'Telnet',
			Timeout => 50,
			Source => '/configs/nas-pb.yml',
			Platform=> 'IOS'
		);
		eval {
			$s->do_login();
			$s->connect(
				Name => 'username',
				Password => 'pass',
			);
			my @temp = $s->cmd('show run');
		};
		if ($#temp>5) {
			$s->close;
			$update='true';
			last;
		}
		$try=$try-1 ;
		if ($try==0) {
			$error = $error.»Some error occure while getting config from «. $host1. «\n»;
			$flag=1;
		}
		$s->close;
	}
	if ($update eq 'true'){
		open (CFG1, «>$path/$host1») || die «Cant open file $path/$host1: $!»;
		print CFG1 @temp;
		close CFG1;
	}
}

Connection and command transfer methods are executed in the context of eval, so that the script does not crash if there are problems with the connection. Just in case, five connection attempts are given. Next, we either write down the error message (we set the flag so that the error leaves the mail) or write the resulting array to a file. The length of the array was checked because some devices periodically return messages like: “it is impossible to issue a configuration, try again later” when they are very busy with something, so that such a message would not be written to the file, because there is no obvious connection error, but the config too not. You can of course parse the output for keywords, but this check is simpler and more universal.
For a new type of device, a new function is added in which you can describe your own logic for working with this type. For example Juniper:
sub juni_scp {
	(my $host1)=@_;
	$try = 5;
	$update = 'false';
	while ($try>0) {
		my $s = Net::SCP::Expect->new(
			user=>'username',
			password=>'pass',
			auto_yes=>1
		);
		eval {
			my @temp= $s->scp("$host1:/config/juniper.conf.gz","$path/$host1.gz" );
		};
		if ($#temp eq "0") {
			$update='true';
			last;
		}
		$try=$try-1 ;
		if ($try==0) {
			$error = $error."Some error occure while getting config from ". $host1. "\n";
			$flag=1;
		}
	}
	if ($update eq 'true'){
		`/usr/bin/gunzip -f $path/$host1.gz`;
	}
}

In this example, instead of telnet, the config is downloaded via scp. An account was created on the device for access to the shell with limited rights.

During the tests, it turned out that sometimes there are lines in the config, which are constantly changing, but do not carry an important semantic load. For example, ntp clock-period in Cisco, or Extreme likes to add an asterisk to the invitation if there are unsaved changes. The function of cutting such lines was added to ignore them and take into account only important changes:
sub str_remove {
	(my $host)=@_;
	open (F,"$path/$host") ; 
	my @f = ; 
	close F; 
	open (F, ">$path/$host"); 
	foreach $str (@f) { 
   		print F $str if ( $str !~ /^ntp clock-period/  and $str !~ /^Current configuration/ and $str !~ /^Slot-/ and $str !~ /^\* Slot-/ and $str !~ /^\*/); 
	} 
	close F;  
}

After passing all the devices from our list, if the flag of the need for an email message was set somewhere, then we send an email in which we include a list of changes, and a variable with errors. We get the list of changes with the standard svn diff, indicating the previous revision number.
sub diff_mail {
	@mail = `/usr/local/bin/svn diff $path -r $prev_rev`;
	open (MAIL, "| /usr/sbin/sendmail user@mydomain.ru, user2@mydomain.ru ")  || die " $!";
	print MAIL <

В конце удаляем локфайл
&unlock("lockfile");

Данный скрипт запускается по крону несколько раз в день. Все внесенные изменения приходят на почту и в любой момент можно любым SVN клиентом подключаться наблюдать за изменениями, сравнивать версии, откатываться и т.д.
Оговорюсь, что я не гуру перлового программирования, и все замечания и советы будут с благодарностью приняты.

Also popular now: