Commit 77f31539 authored by Leigh B Stoller's avatar Leigh B Stoller

Add option to check for and prune stale vlans from switch fabric.

Run as follows:

boss> wap perl snmpit_test --prunestalevlans --impotent

which will tell you about them. Remove the --impotent option to
actually remove them.

Only numbered lans are considered; ones that derive from entries in
the lans table. Named vlans are skipped since those are generally
created by hand (often via the switch CLI).

Caveat; vlans left on trunk links are still a bit of problem since
listvlans returns the other side of the trunk. Needs to be fixed.
parent 49c214c0
......@@ -2977,6 +2977,15 @@ sub ClearReservedVlanTag($;$)
return 0;
}
sub DeleteReservedVlanTag($$)
{
my ($class, $lanid) = @_;
DBQueryWarn("delete from reserved_vlantags where lanid='$lanid'")
or return -1;
return 0;
}
sub GetReservedVlanTags($)
{
my ($self) = @_;
......@@ -3543,6 +3552,34 @@ sub IsNodeInAVlan($$)
return $query_result->numrows;
}
#
# See if there is a vlans table entry for a tag.
#
sub FindTableEntryByTag($$)
{
my ($class, $tag) = @_;
my $query_result =
DBQueryWarn("select * from vlans where tag='$tag'");
return undef
if (!$query_result || !$query_result->numrows);
return $query_result->fetchrow_hashref();
}
sub FindTableEntryByLanid($$)
{
my ($class, $lanid) = @_;
my $query_result =
DBQueryWarn("select * from vlans where id='$lanid'");
return undef
if (!$query_result || !$query_result->numrows);
return $query_result->fetchrow_hashref();
}
sub FindVlanByPort_OLD($$$)
{
my ($class, $experiment, $port) = @_;
......
......@@ -24,7 +24,7 @@
# or merely parse tokens from string and vice-verse must
# use the converters provided in this class.
#
# Copyright (c) 2011, 2012 University of Utah and the Flux Group.
# Copyright (c) 2011-2013 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -649,6 +649,7 @@ sub wire_end($) { return $_[0]->{'WIRE_END'}; }
sub is_switch_side($) { return $_[0]->wire_end() eq "switch"; }
sub wire_type($) { return $_[0]->{'WIRES_ROW'}->{'type'}; }
sub is_trunk_port($) { return $_[0]->wire_type() eq "Trunk"; }
sub is_forced($) { return $_[0]->{"FORCED"};}
sub has_fields($) { return $_[0]->{"HAS_FIELDS"};}
......
......@@ -34,6 +34,7 @@ use strict;
$| = 1; # Turn off line buffering on output
use English;
use Carp;
use SNMP;
use snmpit_lib;
use Carp qw(cluck);
......@@ -214,7 +215,7 @@ sub new($$$;$) {
$self->readifIndex();
# Utah debugging.
$self->{DEBUG} = 1;
$self->{DEBUG} = 0;
return $self;
}
......@@ -511,8 +512,16 @@ sub convertPortFormat($$@) {
$self->debug("Converting ifindex to modport\n",3);
@results = @mps;
goto done;
}
}
my @tmp = @ports;
foreach my $mp (@mps) {
my $port = shift(@tmp);
if (!defined($mp)) {
cluck("convertPortFormat: ".
"bad modport conversion for $port on " .
$self->{NAME} . "\n");
}
}
my @pos = map Port->LookupByStringForced($self->{NAME}.":".$_), @mps;
if ($output == $PORT_FORMAT_NODEPORT) {
......@@ -1207,6 +1216,7 @@ sub removeVlan($@) {
print "Removed VLAN $vlan_number on switch $name.\n";
} else {
print STDERR "Removing VLAN $vlan_number failed on switch $name.\n";
$errors++;
}
# The next call would buy time to let switch consolidate itself
# Nortels have bizarre failures when quickly creating and destroying
......@@ -1313,6 +1323,7 @@ sub listVlans($) {
my ($modport, $node, $ifIndex, @portlist, @memberlist);
$self->debug($self->{NAME} . "::listVlans()\n",1);
my $maxport = $self->{MAXPORT};
my $devicename = $self->{NAME};
#
# Walk the tree to find the VLAN names
......@@ -1353,7 +1364,9 @@ sub listVlans($) {
$modport =~ s/\./\//;
$node = Port->LookupByStringForced($self->{NAME} . ".$modport");
}
push @{$Members{$vlan_number}}, $node->getPCPort();
my $pcport = $node->getPCPort();
$self->debug("$devicename:$vlan_number $node:$pcport\n", 3);
push @{$Members{$vlan_number}}, $pcport;
if (!$Names{$vlan_number}) {
$self->debug("listVlans: WARNING: port $node in non-existant " .
"VLAN $vlan_number\n", 1);
......
......@@ -1009,7 +1009,8 @@ sub getSwitchPrimaryStack($) {
my $result = DBQueryFatal("SELECT stack_id FROM switch_stacks WHERE " .
"node_id='$switch' and is_primary=1");
if (!$result->numrows()) {
print STDERR "No primary stack_id found for switch $switch\n";
print STDERR "No primary stack_id found for switch $switch\n"
if ($debug);
return undef;
} elsif ($result->numrows() > 1) {
print STDERR "Switch $switch is marked as primary in more than one " .
......
......@@ -229,6 +229,12 @@ sub listVlans($) {
my ($vlan_id, $vlan_number, $memberRef) = @$line;
${$vlans{$vlan_id}}[0] = $vlan_number;
push @{${$vlans{$vlan_id}}[1]}, @$memberRef;
if (0 && ! @$memberRef) {
print STDERR
"$vlan_id ($vlan_number) ".
"exists on $devicename with no members\n";
}
}
}
......
......@@ -63,6 +63,7 @@ sub doGetStats($);
sub doVlansFromTables($$@);
sub syncVlansFromTables($$@);
sub doSyncVlansWithDB($);
sub doPruneStaleVlans($);
sub doReset($@);
sub doMakeVlan($$@);
sub doDeleteVlan($@);
......@@ -207,7 +208,8 @@ GetOptions(\%opt,
'y=s','x=s','z=s','F','L=s','O', 'D', 'R', 'f', 'X', 'Z', 'vlan_tag=i',
'of-disable=s', 'of-enable=s', 'of-controller=s', 'of-listener=s',
'o=s@{1,1}', 'redirect-err', 'blockmode', 'syncvlans', 'impotent',
'shadow', 'skip-supplied', 'whol-magic', 'backtraceonwarning');
'shadow', 'skip-supplied', 'whol-magic', 'backtraceonwarning',
'prunestalevlans');
if ($opt{h}) {
exit &usage;
......@@ -433,6 +435,7 @@ if ($opt{b}) { push @commands, ["portstatus"]; }
if ($opt{F}) { push @commands, ["synchleader"]; }
if ($opt{A}) { push @commands, ["reservetags"]; }
if ($opt{'syncvlans'}) { push @commands, ["syncvlans"]; }
if ($opt{'prunestalevlans'}) { push @commands, ["prunestalevlans"]; }
# This option combines with reset/remove
if ($opt{C} && !($opt{r} || $opt{o})) { push @commands, ["unreservetags"]; }
......@@ -699,7 +702,7 @@ COMMAND: foreach my $command (@commands) {
my @vlans;
SWITCH: for ($operation) {
(/listvlans/ || /getstats/ || /vlannumber/ || /synchleader/ ||
/syncvlans/) && do {
/syncvlans/ || /prunestalevlans/) && do {
@devicenames = ($supplied_switches && !$skip_supplied)?
@supplied_switches : getTestSwitches();
last;
......@@ -1178,6 +1181,10 @@ COMMAND: foreach my $command (@commands) {
$exitval += doSyncVlansWithDB(\@stacks);
last;
}; # /listvlans/ && do
/prunestalevlans/ && do {
$exitval += doPruneStaleVlans(\@stacks);
last;
}; # /listvlans/ && do
/listports/ && do {
$exitval += doListPorts(\@stacks);
last;
......@@ -2509,13 +2516,13 @@ sub syncVlansFromTables($$@) {
if (@$stacks[0]->setPortVlan("Control", @controlports)) {
print STDERR "Failed to move some ports back: @controlports\n";
goto bad;
goto bad;
}
}
#
# Look for special vlans that need dual trunking. The vlan becomes
# the "native" vlan, but it must be created before enabling trunking
# the native vlan, but it must be created before enabling trunking
# on the port.
#
foreach my $vlanid (keys(%expvlans)) {
......@@ -4046,3 +4053,139 @@ sub doEnableOpenflowListener($$) {
return $errors;
}
#
# Check for and delete stale vlans.
#
sub doPruneStaleVlans($)
{
my $stacks = shift;
my $errors = 0;
if ($ELABINELAB) {
die "Not allowed to do this inside an ELABINELAB\n";
}
#
# Sanity checking
#
if (@$stacks != 1) {
die "Disabling trunk ports should only involve one stack\n" .
"Stacks are " . join(",",@$stacks) . "\n";
}
my $stack = @$stacks[0];
my %vlans;
my @vlanList = @$stacks[0]->listVlans();
foreach my $vlan (@vlanList) {
my ($id,$tag,$memberref) = @$vlan;
${$vlans{$id}}[0] = $tag;
push @{${$vlans{$id}}[1]}, @$memberref;
}
my @sorted = sort {tbsort($a,$b)} keys %vlans;
foreach my $vlan_id (@sorted) {
my $vlan;
my ($tag,$memberref) = @{$vlans{$vlan_id}};
if ($vlan_id =~ /^\d*$/) {
$vlan = VLan->Lookup($vlan_id);
}
else {
debug("Skipping non-numeric VLAN: $vlan_id ($tag)\n");
next;
}
if (!defined($vlan)) {
my $delete = 1;
print("No such VLAN $vlan_id ($tag) in lans table.\n");
if (@$memberref) {
print " Ports: ";
foreach my $member (@$memberref) {
my $port = Port->Triple2Iface($member);
if (defined($port)) {
print $port;
}
else {
print $member;
}
print " ";
}
print "\n";
}
my $vlantablerow = VLan->FindTableEntryByLanid($vlan_id);
if (defined($vlantablerow)) {
my $pid = $vlantablerow->{'pid'};
my $eid = $vlantablerow->{'eid'};
print(" there is a vlan table entry: $pid,$eid,\n");
my $experiment = Experiment->Lookup($vlantablerow->{'exptidx'});
if (defined($experiment)) {
print " and the experiment exists and is ";
if ($experiment->state() eq EXPTSTATE_ACTIVE() ||
$experiment->state() eq EXPTSTATE_SWAPPING() ||
$experiment->state() eq EXPTSTATE_ACTIVATING()) {
print "active\n";
$delete = 0;
}
else {
print "inactive\n";
}
}
else {
print " but the experiment does not exist.\n";
}
}
my $vlantablerow2 = VLan->FindTableEntryByTag($tag);
if (defined($vlantablerow2) &&
(!defined($vlantablerow) ||
$vlantablerow2->{'id'} != $vlantablerow->{'id'})) {
my $pid = $vlantablerow2->{'pid'};
my $eid = $vlantablerow2->{'eid'};
print(" the tag is in use by: $pid,$eid,\n");
my $experiment = Experiment->Lookup($vlantablerow2->{'exptidx'});
if (defined($experiment)) {
print " and the experiment exists and is ";
if ($experiment->state() eq EXPTSTATE_ACTIVE() ||
$experiment->state() eq EXPTSTATE_SWAPPING() ||
$experiment->state() eq EXPTSTATE_ACTIVATING()) {
print "active\n";
$delete = 0;
}
else {
print "inactive\n";
}
}
else {
print " but the experiment does not exist.\n";
}
}
if ($delete) {
print " so we will delete VLAN $vlan_id ($tag)\n";
if (!$impotent) {
if (!$stack->removeVlan($vlan_id)) {
print STDERR "*** Could not remove vlan: $vlan_id\n";
return -1;
}
#
# Also delete the vlan table rows since they reference
# experiments that no longer exist. Lets do
# reserved_vlantags too.
#
if (defined($vlantablerow)) {
VLan->RecordVLanDeletion($vlantablerow->{'id'});
VLan->DeleteReservedVlanTag($vlantablerow->{'id'});
}
if (defined($vlantablerow2)) {
VLan->RecordVLanDeletion($vlantablerow2->{'id'});
VLan->DeleteReservedVlanTag($vlantablerow2->{'id'});
}
}
}
else {
print " so we will keep the entry.\n";
}
}
}
return 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment