diff options
Diffstat (limited to 'lib/Vyatta/Quagga/Config.pm')
-rw-r--r-- | lib/Vyatta/Quagga/Config.pm | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/lib/Vyatta/Quagga/Config.pm b/lib/Vyatta/Quagga/Config.pm new file mode 100644 index 00000000..107731fe --- /dev/null +++ b/lib/Vyatta/Quagga/Config.pm @@ -0,0 +1,378 @@ +# Author: Robert Bays <robert@vyatta.com> +# 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 $_qcomdelref = ''; + +### Public methods - +# Create the class. +# input: $1 - level of the Vyatta config tree to start at +# $2 - hashref to Quagga add/change command templates +# $3 - hashref to Quagga delete command templates +sub new { + my $that = shift; + my $class = ref ($that) || $that; + my $self = { + _level => shift, + _qcref => shift, + _qcdref => shift, + }; + + $_qcomref = $self->{_qcref}; + $_qcomdelref = $self->{_qcdref}; + + if (! _qtree($self->{_level}, 'delete')) { 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}, 'delete'); + _qtree($self->{_level}, 'set'); +} + +# populate an array reference with Quagga commands +sub returnQuaggaCommands { + my ($self, $arrayref) = @_; + my $key; + my $string; + + foreach $key (sort { $b cmp $a } keys %_vtyshdel) { + foreach $string (@{$_vtyshdel{$key}}) { + push @{$arrayref}, "$string"; + } + } + + foreach $key (sort keys %_vtysh) { + foreach $string (@{$_vtysh{$key}}) { + push @{$arrayref}, "$string"; + } + } + + return 1; +} + +# methods to send the commands to Quagga +sub setConfigTree { + my ($self, $level) = @_; + if (_setConfigTree($level, 0, 0)) { return 1; } + return 0; +} + +sub setConfigTreeRecursive { + my ($self, $level) = @_; + if (_setConfigTree($level, 0, 1)) { return 1; } + return 0; +} + +sub deleteConfigTree { + my ($self, $level) = @_; + if (_setConfigTree($level, 1, 0)) { return 1; } + return 0; +} + +sub deleteConfigTreeRecursive { + my ($self, $level) = @_; + if (_setConfigTree($level, 1, 1)) { 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 +# output: none, return failure if needed +sub _setConfigTree { + my ($level, $delete, $recurse) = @_; + + 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\n"; } + + my $key; + my @keys; + foreach $key (sort $sortfunc keys %$vtyshref) { + if ($_DEBUG >= 3) { print "DEBUG: _setConfigTree - key $key\n"; } + + if ((($recurse) && ($key =~ /^$level/)) || ((! $recurse) && ($key =~ /^$level$/))) { + my ($index, $cmd); + $index = 0; + foreach $cmd (@{$vtyshref->{$key}}) { + if ($_DEBUG >= 2) { print "DEBUG: _setConfigTree - key: $key \t cmd: $cmd\n"; } + + if (! _sendQuaggaCommand("$cmd")) { 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 +# output: none, return failure if needed +sub _sendQuaggaCommand { + my ($command) = @_; + my $section; + my $args = "/usr/sbin/vtysh --noerr -c 'configure terminal' "; + + my @commands = split / ; /, $command; + foreach $section (@commands) { + $args .= "-c '$section' "; + } + + if ($_DEBUG >= 2) { print "DEBUG: _sendQuaggaCommand - args prior to system call - $args\n"; } + system("$args"); + if ($? != 0) { + # TODO: note that DEBUG will never happen here with --noerr as an argument. + # need to fix --noerr. Also probably need to code a way to conditionally use --noerr. + if ($_DEBUG) { + print "DEBUG: _sendQuaggaCommand - vtysh failure $? - $args\n"; + print "\n"; + } + return 0; + } + + 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 = ''; + my $token; + # 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 $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 ($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 correctly referenced %qcom. i.e. add or delete %qcom. +# input: $1 - Vyatta config tree string +# $2 - Quagga command template hash +# output: %qcom hash key to corresponding Quagga command template string +sub _qCommandFind { + my $vyattaconfig = shift; + my $qcom = shift; + my $token = ''; + 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 $token (@nodes) { + if (exists $qcom->{$token}) { $command = $token; } + elsif (exists $qcom->{"$command $token"}) { $command = "$command $token"; } + elsif (exists $qcom->{"$command var"}) { $command = "$command var"; } + else { return undef; } + } + + # return hash key if Quagga command template string is found + if (defined $qcom->{$command}) { 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); + + + # It's ugly that I have to create a new Vyatta config object every time, + # but something gets messed up on the stack if I don't. not sure + # what yet. would love to reference a global config and just reset Level. + my $config = new Vyatta::Config; + $config->setLevel($level); + + # setup references for set or delete action + if ($action eq 'set') { + $qcom = $_qcomref; + $vtysh = \%_vtysh; + + @nodes = $config->listNodes(); + } + else { + $qcom = $_qcomdelref; + $vtysh = \%_vtyshdel; + + @nodes = $config->listDeleted(); + } + + 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) { + my $node; + foreach $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 'delete') || ($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", $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 $val = undef; + if ($action eq 'set') { $val = $config->returnValue($node); } + else { $val = $config->returnOrigValue($node); } + + # is this a leaf node? + if ($val) { + my $var = _qVarReplace("$level $node $val", $qcom->{$qcommand}); + 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}); + 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", 'delete'); + _qtree("$level $node", 'set'); + } + } + + return 1; +} + +return 1; |