# Author: Robert Bays # Date: 2010 # Description: interface between Vyatta templates and Quagga vtysh # **** License **** # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2010 Vyatta, Inc. # All Rights Reserved. # **** End License **** package Vyatta::Quagga::Config; use strict; use warnings; use lib "/opt/vyatta/share/perl5/"; use Vyatta::Config; my $_DEBUG = 0; my %_vtysh; my %_vtyshdel; my $_qcomref = ''; my $_vtyshexe = '/usr/bin/vtysh'; ### Public methods - # Create the class. # input: $1 - level of the Vyatta config tree to start at # $2 - hash of hashes ref to Quagga set/delete command templates sub new { my $that = shift; my $class = ref ($that) || $that; my $self = { _level => shift, _qcref => shift, }; $_qcomref = $self->{_qcref}; if (! _qtree($self->{_level}, 'del')) { return 0; } if (! _qtree($self->{_level}, 'set')) { return 0; } bless $self, $class; return $self; } # Set/check debug level # input: $1 - debug level sub setDebugLevel { my ($self, $level) = @_; if ($level > 0) { $_DEBUG = $level; return $level; } return 0; } # reinitialize the vtysh hashes for troublshooting tree # walk post object creation sub _reInitialize { my ($self) = @_; %_vtysh = (); %_vtyshdel = (); _qtree($self->{_level}, 'del'); _qtree($self->{_level}, 'set'); } # populate an array reference with Quagga commands sub returnQuaggaCommands { my ($self, $arrayref) = @_; foreach my $key (sort { $b cmp $a } keys %_vtyshdel) { foreach my $string (@{$_vtyshdel{$key}}) { push @{$arrayref}, "$string"; } } foreach my $key (sort keys %_vtysh) { foreach my $string (@{$_vtysh{$key}}) { push @{$arrayref}, "$string"; } } return 1; } # methods to send the commands to Quagga sub setConfigTree { my ($self, $level, @skip_list) = @_; if (_setConfigTree($level, 0, 0, @skip_list)) { return 1; } return 0; } sub setConfigTreeRecursive { my ($self, $level, @skip_list) = @_; if (_setConfigTree($level, 0, 1, @skip_list)) { return 1; } return 0; } sub deleteConfigTree { my ($self, $level, @skip_list) = @_; if (_setConfigTree($level, 1, 0, @skip_list)) { return 1; } return 0; } sub deleteConfigTreeRecursive { my ($self, $level, @skip_list) = @_; if (_setConfigTree($level, 1, 1, @skip_list)) { return 1; } return 0; } ### End Public methods - ### Private methods # traverse the set/delete trees and send commands to quagga # set traverses from $level in tree top down. # delete traverses from bottom up in tree to $level. # execute commands in tree one at a time. If there is an error in vtysh, # fail. otherwise, remove command from tree on success as we may traverse # this portion of the tree again otherwise. # input: $1 - level of the tree to start at # $2 - delete bool # $3 - recursive bool # $4 - arrays of strings to skip # output: none, return failure if needed sub _setConfigTree { my ($level, $delete, $recurse, @skip_list) = @_; my $qcom = $_qcomref; if ((! defined $level) || (! defined $delete) || (! defined $recurse)) { return 0; } # default tree is the set vtysh hash my $vtyshref = \%_vtysh; # default tree walk order is top down my $sortfunc = \&cmpf; # if this is delete, use delete vtysh hash and walk the tree bottom up if ($delete) { $vtyshref = \%_vtyshdel; $sortfunc = \&cmpb; } if ($_DEBUG >= 3) { print "DEBUG: _setConfigTree - enter - level: $level\tdelete: $delete\trecurse: $recurse\tskip: "; foreach my $key (@skip_list) { print "$key "; } print "\n"; } # This loop walks the list of commands and sends to quagga if appropriate foreach my $key (sort $sortfunc keys %$vtyshref) { if ($_DEBUG >= 3) { print "DEBUG: _setConfigTree - key $key\n"; } # skip parameters in skip_list my $found = 0; if ((scalar @skip_list) > 0) { foreach my $node (@skip_list) { if ($key =~ /$node/) { $found = 1; if ($_DEBUG >= 3) { print "DEBUG: _setConfigTree - key $node in skip list\n"; } } } } if ($found) { next; } # should we run the vtysh command with noerr? my $noerr = ''; if ( (defined $qcom->{$key}->{'noerr'}) && ( ($qcom->{$key}->{'noerr'} eq "both") || (($qcom->{$key}->{'noerr'} eq "del") && ($delete)) || (($qcom->{$key}->{'noerr'} eq "set") && (!$delete)))) { $noerr = 1; } # this conditional matches key to level exactly or if recurse, start of key to level if ((($recurse) && ($key =~ /^$level/)) || ((! $recurse) && ($key =~ /^$level$/))) { my $index = 0; foreach my $cmd (@{$vtyshref->{$key}}) { if ($_DEBUG >= 2) { print "DEBUG: _setConfigTree - key: $key \t cmd: $cmd\n"; } if (! _sendQuaggaCommand("$cmd", "$noerr")) { return 0; } # remove this command so we don't hit it again in another Recurse call delete ${$vtyshref->{$key}}[$index]; $index++; } } } return 1; } # sort subs for _setConfigTree sub cmpf { $a cmp $b } sub cmpb { $b cmp $a } # properly format a Quagga command for vtysh and send to Quagga # input: $1 - qVarReplaced Quagga Command string # $2 - boolean: should we use noerr? # output: none, return failure if needed sub _sendQuaggaCommand { my ($command, $noerr) = @_; my @arg_array = ("$_vtyshexe"); if ($noerr) { push (@arg_array, '--noerr'); } if ($_DEBUG >= 2) { push (@arg_array, '-E'); } push (@arg_array, '-c'); push (@arg_array, 'configure terminal'); my @commands = split / ; /, $command; foreach my $section (@commands) { push (@arg_array, '-c'); push (@arg_array, "$section"); } system(@arg_array) == 0 or _logger(\@arg_array, 1); return 1; } # log error message to syslog, optionally die # input: $1 - reference to error message array # $2 - die boolean sub _logger { my $error_array = shift; my $die = shift; my @logger_cmd = ("/usr/bin/logger"); push (@logger_cmd, "-i"); push (@logger_cmd, "-t vyatta-cfg-quagga"); if ($_DEBUG) { push (@logger_cmd, "-s"); } push (@logger_cmd, "@{$error_array} failed: $?"); system(@logger_cmd) == 0 or die "unable to log system error message."; if ($die) { die "Error configuring routing subsystem. See log for more detailed information\n"; } return 1; } # translate a Vyatta config tree into a Quagga command using %qcom as a template. # input: $1 - Vyatta config tree string # $2 - Quagga command template string # output: Quagga command suitable for vtysh as defined by %qcom. sub _qVarReplace { my $node = shift; my $qcommand = shift; if ($_DEBUG >= 2) { print "DEBUG: _qVarReplace entry: node - $node\n"; print "DEBUG: _qVarReplace entry: qcommand - $qcommand\n"; } my @nodes = split /\s/, $node; my @qcommands = split /\s/, $qcommand; my $result = ''; # try to replace (#num, ?var) references foreach item in Quagga command template array # with their corresponding value in Vyatta command array at (#num) index foreach my $token (@qcommands) { # is this a #var reference? if so translate and append to result if ($token =~ s/\#(\d+);*/$1/) { $token--; $result="$result $nodes[$token]"; } # is this a ?var reference? if so check for existance of the var in Vyatta Config # tree and conditionally append. append token + value. These conditional vars # will only work at EOL in template string. elsif ($token =~ s/\?(\w+);*/$1/) { # TODO: Vyatta::Config needs to be fixed to accept level in constructor my $config = new Vyatta::Config; $config->setLevel($node); my $value = $config->returnValue($token); if ($value) { $result = "$result $token $value"; } elsif ($config->exists($token)) { $result = "$result $token"; } } # is this a @var reference? if so, append just the value instead of token + value elsif ($token =~ s/\@(\w+);*/$1/) { my $config = new Vyatta::Config; $config->setLevel($node); my $value = $config->returnValue($token); if (defined $value) { $result = "$result $value"; } } # if not, just append string to result else { $result = "$result $token"; } } # remove leading space characters $result =~ s/^\s(.+)/$1/; if ($_DEBUG >= 2) { print "DEBUG: _qVarReplace exit: result - $result\n"; } return $result; } # For given Vyatta config tree string, find a corresponding Quagga command template # string as defined in %qcom # input: $1 - Vyatta config tree string # $2 - action (set|del) # $3 - Quagga command template hash # output: %qcom hash key to corresponding Quagga command template string sub _qCommandFind { my $vyattaconfig = shift; my $action = shift; my $qcom = shift; my $command = ''; my @nodes = split /\s+/, $vyattaconfig; # append each token in the Vyatta config tree. sequentially # check if there is a corresponding hash in %qcom. if not, # do same check again replacing the end param with var to see # if this is a var replacement foreach my $token (@nodes) { if (exists $qcom->{$token}->{$action}) { $command = $token; } elsif (exists $qcom->{"$command $token"}->{$action}) { $command = "$command $token"; } elsif (exists $qcom->{"$command var"}->{$action}) { $command = "$command var"; } else { return undef; } } # return hash key if Quagga command template string is found if (defined $qcom->{$command}->{$action}) { return $command; } else { return undef; } } # translate the adds/changes in a Vyatta config tree into Quagga vtysh commands. # recursively walks the tree. # input: $1 - the level of the Vyatta config tree to start at # $2 - the action (set|delete) # output: none - creates the %vtysh that contains the Quagga add commands sub _qtree { my ($level, $action) = @_; my @nodes; my ($qcom, $vtysh); $qcom = $_qcomref; # Would love to reference a global config and just reset Levels, # but Vyatta::Config isn't recursion safe. my $config = new Vyatta::Config; $config->setLevel($level); # setup references for set or delete action if ($action eq 'set') { $vtysh = \%_vtysh; @nodes = $config->listNodes(); } else { $vtysh = \%_vtyshdel; @nodes = $config->listDeleted(); # handle special case for multi-nodes values being deleted # listDeleted() doesn't return the node as deleted if it is a multi # unless all values are deleted. # TODO: fix listDeleted() in Config.pm # This is really, really fugly. my @all_nodes = $config->listNodes(); foreach my $node (@all_nodes) { my @array = split /\s+/, $level; push @array, $node; my ($multi, $text, $default) = $config->parseTmpl(\@array); if ($multi) { my @orig_values = $config->returnOrigValues("$node"); my @new_values = $config->returnValues("$node"); my %chash = $config->compareValueLists(\@orig_values, \@new_values); if (${$chash{'deleted'}}[0]) { push @nodes, $node; } } } } ## end else { if ($_DEBUG) { print "DEBUG: _qtree - action: $action\tlevel: $level\n"; } # traverse the Vyatta config tree and translate to Quagga commands where apropos if (@nodes > 0) { foreach my $node (@nodes) { if ($_DEBUG >= 2) { print "DEBUG: _qtree - foreach node loop - node $node\n"; } # for set action, need to check that the node was actually changed. Otherwise # we end up re-writing every node to Quagga every commit, which is bad. Mmm' ok? if (($action eq 'del') || ($config->isChanged("$node"))) { # is there a Quagga command template? # TODO: need to add function reference support to qcom hash for complicated nodes my $qcommand = _qCommandFind("$level $node", $action, $qcom); # if I found a Quagga command template, then replace any vars if ($qcommand) { # get the apropos config value so we can use it in the Quagga command template my @vals = undef; # This is either a set or delete on a single or multi: node if ($action eq 'set') { my $tmplhash = $config->parseTmplAll(join ' ', "$level $node"); if ($tmplhash->{'multi'}) { if ($_DEBUG > 2) { print "DEBUG: multi\n"; } @vals = $config->returnValues($node); } else { if ($_DEBUG > 2) { print "DEBUG: not a multi\n"; } $vals[0] = $config->returnValue($node); } } else { my $tmplhash = $config->parseTmplAll(join ' ', "$level $node"); if ($tmplhash->{'multi'}) { if ($_DEBUG > 2) { print "DEBUG: multi\n"; } @vals = $config->returnOrigValues($node); } else { if ($_DEBUG > 2) { print "DEBUG: not a multi\n"; } $vals[0] = $config->returnOrigValue($node); } } # is this a leaf node? if (defined $vals[0]) { foreach my $val (@vals) { my $var = _qVarReplace("$level $node $val", $qcom->{$qcommand}->{$action}); push @{$vtysh->{"$qcommand"}}, $var; if ($_DEBUG) { print "DEBUG: _qtree leaf node command: set $level $action $node $val \n\t\t\t\t\t$var\n"; } } } else { my $var = _qVarReplace("$level $node", $qcom->{$qcommand}->{$action}); push @{$vtysh->{"$qcommand"}}, $var; if ($_DEBUG) { print "DEBUG: _qtree node command: set $level $action $node \n\t\t\t\t$var\n"; } } } } # recurse to next level in tree _qtree("$level $node", 'del'); _qtree("$level $node", 'set'); } } return 1; } return 1;