From 81b51bb9270e77289604493761a2f7cb047bd3ea Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Tue, 15 Jul 2008 14:13:51 -0700 Subject: Look for changes in QoS after commit Since configuration system doesn't correctly notify on addition/deletion, have to introduce this extra verbosity to have each traffic-shaper type check for changes. Bugfix for 3452 --- scripts/VyattaQosFairQueue.pm | 16 ++++- scripts/VyattaQosRateLimiter.pm | 13 ++++ scripts/VyattaQosTrafficShaper.pm | 50 ++++++++++++++ scripts/vyatta-qos.pl | 135 ++++++++++++++++++++++++++++++-------- 4 files changed, 184 insertions(+), 30 deletions(-) (limited to 'scripts') diff --git a/scripts/VyattaQosFairQueue.pm b/scripts/VyattaQosFairQueue.pm index 8a9dfdf..278bb77 100644 --- a/scripts/VyattaQosFairQueue.pm +++ b/scripts/VyattaQosFairQueue.pm @@ -37,8 +37,8 @@ sub new { my $class = ref($that) || $that; my $self = {%fields}; - $self->{_perturb} = $config->returnValue("hash-interval"); - $self->{_limit} = $config->returnValue("queue-limit"); + $self->{_perturb} = $config->returnValue('hash-interval'); + $self->{_limit} = $config->returnValue('queue-limit'); return bless $self, $class; } @@ -51,4 +51,16 @@ sub commands { print "\n"; } +sub isChanged { + my ( $self, $name ) = @_; + my $config = new VyattaConfig; + + $config->setLevel("qos-policy fair-queue $name"); + foreach my $attr ('hash-interval', 'queue-limit') { + if ($config->isChanged($attr)) { + return $attr + } + } + return undef; # false +} 1; diff --git a/scripts/VyattaQosRateLimiter.pm b/scripts/VyattaQosRateLimiter.pm index 548526b..f519683 100644 --- a/scripts/VyattaQosRateLimiter.pm +++ b/scripts/VyattaQosRateLimiter.pm @@ -55,4 +55,17 @@ sub commands { $dev, $self->{_rate}, $self->{_latency}, $self->{_burst}; } +sub isChanged { + my ($self, $name) = @_; + my $config = new VyattaConfig; + + $config->setLevel("qos-policy rate-limit $name"); + foreach my $attr ('bandwidth', 'burst', 'latency') { + if ($config->isChanged($attr)) { + return $attr + } + } + return undef; # false +} + 1; diff --git a/scripts/VyattaQosTrafficShaper.pm b/scripts/VyattaQosTrafficShaper.pm index f321e9b..325c02e 100644 --- a/scripts/VyattaQosTrafficShaper.pm +++ b/scripts/VyattaQosTrafficShaper.pm @@ -404,4 +404,54 @@ sub commands { } } +# Walk configuration tree and look for changed nodes +# The configuration system should do this but doesn't do it right +sub isChanged { + my ($self, $name) = @_; + my $config = new VyattaConfig; + + $config->setLevel("qos-policy traffic-shaper $name"); + + if ($config->isChanged('bandwidth') ) { + return 'bandwidth'; + } + + foreach my $attr ('bandwidth', 'burst', 'ceiling', 'priority', 'queue-limit', 'queue-type') { + if ($config->isChanged("default $attr")) { + return "default $attr"; + } + } + + my %classNodes = $config->listNodeStatus('class'); + while (my ($class, $status) = each %classNodes) { + if ($status ne 'static') { + return "class $class"; + } + + foreach my $attr ('bandwidth', 'burst', 'ceiling', 'priority', 'queue-limit', 'queue-type') { + if ($config->isChanged("class $class $attr")) { + return "class $class $attr"; + } + } + + my %matchNodes = $config->listNodeStatus("class $class match"); + while (my ($match, $status) = each %matchNodes) { + my $level = "class $class match $match"; + if ($status ne 'static') { + return $level; + } + + foreach my $parm ('vif', 'interface', 'ip dscp', 'ip protocol', + 'ip source address', 'ip destination address', + 'ip source port', 'ip destination port') { + if ($config->isChanged("$level $parm")) { + return "$level $parm"; + } + } + } + } + + return undef; # false +} + 1; diff --git a/scripts/vyatta-qos.pl b/scripts/vyatta-qos.pl index ad97617..3d8e58b 100755 --- a/scripts/vyatta-qos.pl +++ b/scripts/vyatta-qos.pl @@ -21,24 +21,22 @@ use strict; use Getopt::Long; my $debug = $ENV{'QOS_DEBUG'}; -my $check = undef; +my ($check, $update, $applyChanges); my @updateInterface = (); my @deleteInterface = (); -my $listPolicy = undef; -my $deletePolicy = undef; +my ($listPolicy, $deletePolicy); my @createPolicy = (); -my @updatePolicy = (); GetOptions( "check" => \$check, + "apply-changes" => \$applyChanges, "update-interface=s{3}" => \@updateInterface, "delete-interface=s{2}" => \@deleteInterface, "list-policy" => \$listPolicy, "delete-policy=s" => \$deletePolicy, "create-policy=s{2}" => \@createPolicy, - "update-policy=s{2}" => \@updatePolicy, ); # class factory for policies @@ -135,19 +133,92 @@ sub update_interface { die "Unknown qos-policy $name\n"; } -sub delete_policy { +sub using_policy { + my ($config, $name, $interface) = @_; + my @inuse = (); + + foreach my $dir ( $config->listNodes("$interface qos-policy") ) { + my $policy = $config->returnValue("$interface qos-policy $dir"); + if ($policy eq $name) { + push @inuse, "$interface $dir"; + } + } + return @inuse; +} + +sub interfaces_using { my ($name) = @_; my $config = new VyattaConfig; + my @affected = (); + + $config->setLevel('interfaces'); + foreach my $type ( $config->listNodes() ) { + foreach my $interface ( $config->listNodes($type) ) { + push @affected, using_policy($config, $name, "$type $interface"); + + if ($type eq 'ethernet') { + foreach my $vif ( $config->listNodes("$type $interface vif") ) { + push @affected, using_policy($config, $name, "$type $interface vif $vif"); + } + } - $config->setLevel("interfaces ethernet"); - foreach my $interface ( $config->listNodes() ) { - foreach my $direction ( $config->listNodes("$interface qos-policy") ) { - if ($config->returnValue("$interface qos-policy $direction") eq $name) { - # can't delete active policy - die "Qos policy $name still in use on ethernet $interface $direction\n"; + if ($type eq 'adsl') { + foreach my $pvc ( $config->listNodes("adsl $interface pvc") ) { + foreach my $pvctype ( $config->listNodes("adsl $interface pvc $pvc") ) { + foreach my $vc ( $config->listNodes("adsl $interface pvc $pvc $pvctype") ) { + push @affected, using_policy($config, $name, + "adsl $interface pvc $pvc $pvctype $vc"); + } + } + } } } } + + return @affected; +} + +sub etherName { + my $eth = shift; + + if ($_ =~ /vif/) { + shift; + $eth .= $_; + } + return $eth; +} + +sub serialName { + my $wan = shift; + # XXX add vif + return $wan; +} + +sub adslName { + # adsl-name pvc pvc-num ppp-type id + my (undef, undef, undef, $type, $id) = @_; + + return $type . $id; +} + +# Handle mapping of interface types to device names +my %interfaceTypes = ( + 'ethernet' => \ðerName, + 'serial' => \&serialName, + 'adsl' => \&adslName, + ); + +sub delete_policy { + my ($name) = @_; + my @inuse = interfaces_using($name); + + if ( @inuse ) { + foreach my $usage (@inuse) { + warn "QoS policy $name used by $usage\n"; + } + # can't delete active policy + die "Must delete QoS policy from interfaces before deleting rules\n"; + } } sub check_conflict { @@ -172,18 +243,24 @@ sub create_policy { make_policy($config, $shaper, $name); } -sub update_policy { - my ($shaper, $name) = @_; +sub apply_changes { my $config = new VyattaConfig; - # Syntax check - make_policy($config, $shaper, $name); - - $config->setLevel("interfaces ethernet"); - foreach my $interface ( $config->listNodes() ) { - foreach my $direction ( $config->listNodes("$interface qos-policy") ) { - if ($config->returnValue("$interface qos-policy $direction") eq $name) { - update_interface($interface, $direction, $name); + $config->setLevel('qos-policy'); + foreach my $policy ($config->listNodes()) { + foreach my $name ($config->listNodes($policy)) { + my $shaper = make_policy($config, $policy, $name); + + if ($shaper->isChanged($name)) { + foreach my $cfgpath (interfaces_using($name)) { + my @elements = split / /, $cfgpath; + my $direction = pop @elements; # out, in, ... + my $type = shift @elements; # ethernet, serial, ... + my $interface = $interfaceTypes{$type}; + my $device = $interface->(@elements); + + update_interface($device, $direction, $name); + } } } } @@ -214,22 +291,24 @@ if ( $#createPolicy == 1) { exit 0; } -if ( $#updatePolicy == 1) { - update_policy(@updatePolicy); - exit 0; -} - if ( $deletePolicy ) { delete_policy($deletePolicy); exit 0; +} + +if ( $applyChanges ) { + apply_changes(); + exit 0; } print <