Commit 49a88ef0 authored by Robert Ricci's avatar Robert Ricci
Browse files

Check in some changes from Keith Slower @Berkeley. He has some changes

to the Cisco code too, but I want to do more investigation of them
before committing.
parent e0c33c75
......@@ -164,6 +164,40 @@ sub new($$$;$) {
return $self;
}
# Attempt to repeat an action until it succeeds
sub hammer($$$;$) {
my ($self, $closure, $id, $retries) = @_;
if (!defined($retries)) { $retries = 8; }
for my $i (1 .. $retries) {
my $result = $closure->();
if (defined($result) || ($retries == 1)) { return $result; }
if (defined($id)) { warn $id . " ... will try again\n"; }
sleep 1;
}
if (defined($id)) { warn $id . " .. giving up\n"; }
return undef;
}
# shorthand
sub get1($$$) {
my ($self, $obj, $instance) = @_;
my $id = "nortel::snmpitGetOnePersistently of $obj, $instance";
my $closure = sub () {
my $RetVal = snmpitGet($self->{SESS}, [$obj, $instance], 1);
if (!defined($RetVal)) { sleep 2;}
return $RetVal;
};
my $RetVal = $self->hammer($closure, $id, 10);
if (!defined($RetVal)) {
warn "$id failed - $snmpit_lib::snmpitErrorString\n";
}
return $RetVal;
}
#
# Set a variable associated with a port. The commands to execute are given
# in the cmdOIs hash above
......@@ -290,10 +324,30 @@ sub convertPortFormat($$@) {
return undef;
}
#
# Check to see if the given 802.1Q VLAN tag exists on the switch
#
# usage: vlanNumberExists($self, $vlan_number)
# returns 1 if the VLAN exists, 0 otherwise
#
sub vlanNumberExists($$) {
my ($self, $vlan_number) = @_;
my $obj = "snVLanByPortCfgVLanName.$vlan_number";
my $rv = $self->{SESS}->get($obj);
if (!defined($rv) || !$rv || ($rv eq "NOSUCHINSTANCE")) {
sleep 1;
$rv = $self->{SESS}->get($obj);
if (defined($rv) && $rv && ($rv ne "NOSUCHINSTANCE")) {
return 1;
} else { return 0; }
}
return 1;
}
#
# Given VLAN indentifiers from the database, finds the 802.1Q VLAN
# number for them. If not VLAN id is given, returns mappings for the entire
# number for them. If no VLAN id is given, returns mappings for the entire
# switch.
#
# usage: findVlans($self, @vlan_ids)
......@@ -304,9 +358,9 @@ sub findVlans($@) {
my $self = shift;
my @vlan_ids = @_;
my %mapping = ();
my ($count, $name, $id, $vlan_number, $vlan_name) = ( scalar (@vlan_ids));
my ($name, $id, $vlan_number, $vlan_name);
if ($count == 0) {
if (!@vlan_ids) {
while ( ($id, $name) = each %{$self->{NEW_NAMES}} ) {
$mapping{$id} = $name;
}
......@@ -315,7 +369,6 @@ sub findVlans($@) {
foreach $id (@vlan_ids) {
if (defined($self->{NEW_NAMES}->{$id})) {
$mapping{$id} = $self->{NEW_NAMES}->{$id};
if (--$count == 0) { return %mapping };
}
}
}
......@@ -326,19 +379,16 @@ sub findVlans($@) {
$self->{SESS}->getnext($field);
do {
($name,$vlan_number,$vlan_name) = @{$field};
$self->debug("findVlan: Got $name $vlan_number $vlan_name\n",2);
$self->debug("foundry::findVlans: $name $vlan_number $vlan_name\n",2);
#
# We only want the names - we ignore everything else
#
if ($name =~ /snVLanByPortCfgVLanName/) {
if (($count <= 0) || exists $mapping{$vlan_name}) {
if (!@vlan_ids || exists $mapping{$vlan_name}) {
$self->debug("Putting in mapping from $vlan_name to " .
"$vlan_number\n",2);
$id = $mapping{$vlan_name};
$mapping{$vlan_name} = $vlan_number;
if (!defined($id) && (--$count == 0))
{ return %mapping };
}
}
......@@ -389,7 +439,7 @@ sub findVlan($$;$) {
}
}
}
$self->debug("Leaving device findVlan of $vlan_id \n");
$self->debug("Leaving device findVlan of $vlan_id \n",2);
return $vlan_number;
}
......@@ -448,6 +498,7 @@ sub setPortVlan($$@) {
my $errors = 0;
my $vlan_id = $self->{NEW_NUMBERS}->{$vlan_number};
my %BumpedVlans = ();
#
# Run the port list through portmap to find the ports on the switch that
......@@ -475,11 +526,16 @@ sub setPortVlan($$@) {
$RetVal = $self->{SESS}->get(["snSwPortInfoTagMode",$portIndex]);
$self->debug("TagMode for portIndex $portIndex is $RetVal\n");
if (defined($RetVal) && ($RetVal eq "untagged")) {
$self->debug("2nd chance at $port return $RetVal\n");
$self->clearAllVlansOnTrunk($port);
$RetVal = $self->{SESS}->set( $obj, 4);
if (defined ($RetVal) && $RetVal ) {
next;
$self->debug("2nd chance at $port\n");
$RetVal = $self->{SESS}->get(["snSwPortVlanId",$portIndex]);
if (defined($RetVal)) {
$BumpedVlans{$RetVal} = 1;
my $ok = $self->setVlansOnTrunk($port,0,$RetVal);
$self->debug("sVOT( $port, 0, $RetVal) returned $ok\n");
$RetVal = $self->{SESS}->set( $obj, 4);
if (defined ($RetVal) && $RetVal ) {
next;
}
}
}
print STDERR "VLAN change for index $port failed\n";
......@@ -502,8 +558,8 @@ sub setPortVlan($$@) {
#
if (defined($vlan_id)) {
# print " Creating VLAN $vlan_id as VLAN #$vlan_number on " .
# "$self->{NAME} ... ";
print " Creating VLAN $vlan_id as VLAN #$vlan_number on " .
"$self->{NAME} ... ";
my $obj = "snVLanByPortCfgStpMode";
my $RetVal = $self->{SESS}->set( [$obj, $vlan_number,0,"INTEGER"]);
if (!$RetVal) {
......@@ -519,6 +575,16 @@ sub setPortVlan($$@) {
print "",($RetVal? "Succeeded":"Failed"), ".\n";
}
#
# When removing things from the control vlan for a firewall,
# need to tell stack to shake things up to flush FDB on neighboring
# switches.
#
my @bumpedlist = keys ( %BumpedVlans );
if (@bumpedlist) {
@{$self->{DISPLACED_VLANS}} = @bumpedlist;
}
return $errors;
}
......@@ -667,6 +733,22 @@ sub UpdateField($$$@) {
return $result;
}
#
# Determine if a VLAN has any members
# (Used by stack->switchesWithPortsInVlan())
#
sub vlanHasPorts($$) {
my ($self, $vlan_number) = @_;
my $portset = $self->get1("snVLanByPortPortList",$vlan_number);
if (defined($portset)) {
my @ports = unpack "C*", $portset;
$self->debug("foundry::vlanHasPorts got @ports \n");
if (@ports) { return 1; }
}
return 0;
}
#
# List all VLANs on the device
#
......@@ -938,6 +1020,51 @@ sub getStats ($) {
}
#
# Used to flush FDB entries easily
#
sub resetVlanIfOnTrunk($$$) {
my ($self, $modport, $vlan) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
my $obj = "snVLanByPortMemberRowStatus.$vlan.$ifIndex";
my $RetVal = $self->{SESS}->get($obj);
if ($self->{DEBUG} > 0) {
print $self->{NAME} . ":resetVlanIfOnTrunk got $RetVal for $obj\n";
}
if (defined($RetVal) && ($RetVal eq "valid")) {
$self->setVlansOnTrunk($modport,0,$vlan);
$self->setVlansOnTrunk($modport,1,$vlan);
}
return 0;
}
#
# Get the ifindex for an EtherChannel (trunk given as a list of ports)
#
# usage: getChannelIfIndex(self, ports)
# Returns: undef if more than one port is given, and no channel is found
# an ifindex if a channel is found and/or only one port is given
#
# N.B. by Sklower - cisco's use this and so it gets called from _stack.pm
#
sub getChannelIfIndex($@) {
my $self = shift;
my @ports = @_;
my @ifIndexes = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports);
my $ifindex = undef;
#
# Try to get a channel number for each one of the ports in turn - we'll
# take the first one we get
#
foreach my $port (@ifIndexes) {
if ($port) { $ifindex = $port; last; }
}
return $ifindex;
}
#
# Enable, or disable, port on a trunk
#
......@@ -1075,6 +1202,7 @@ sub enablePortTrunking($$$) {
warn "ERROR: Unable to set native VLAN on trunk\n";
return 0;
}
return 1;
}
#
......
This diff is collapsed.
......@@ -2,9 +2,9 @@
#
# EMULAB-LGPL
# Copyright (c) 2004, Regents, University of California.
# Modified from an Netbed/Emulab module, Copyright (c) 2000-2003, University of
# Utah
# Copyright (c) 2000-2005 University of Utah and the Flux Group.
# Copyright (c) 2004-2006 Regents, University of California.
# All rights reserved.
#
package snmpit_stack;
......@@ -24,8 +24,6 @@ use libtestbed;
# so that the object knows which to connect to. A future version may not
# require the device list, and dynamically connect to devices as appropriate
#
# the stack_id happens to also be the name of the stack leader.
#
# usage: new(string name, string stack_id, int debuglevel, list of devicenames)
# returns a new object blessed into the snmpit_stack class
#
......@@ -37,7 +35,7 @@ sub new($$$@) {
my $class = ref($proto) || $proto;
my $stack_id = shift;
my $debugLevel = shift;
my $debuglevel = shift;
my @devicenames = @_;
#
......@@ -49,8 +47,8 @@ sub new($$$@) {
#
# Set up some defaults
#
if (defined $debugLevel) {
$self->{DEBUG} = $debugLevel;
if (defined $debuglevel) {
$self->{DEBUG} = $debuglevel;
} else {
$self->{DEBUG} = 0;
}
......@@ -58,7 +56,6 @@ sub new($$$@) {
$self->{STACKID} = $stack_id;
$self->{MAX_VLAN} = 4095;
$self->{MIN_VLAN} = 2;
#
# The name of the leader of this stack. We fall back on the old behavior of
# using the stack name as the leader if the leader is not set
......@@ -74,6 +71,17 @@ sub new($$$@) {
#
@{$self->{DEVICENAMES}} = @devicenames;
#
# The following two lines will let us inherit snmpit_cisco_stack
# (someday), (and can help pare down lines of diff in the meantime)
#
$self->{PRUNE_VLANS} = 1;
$self->{VTP} = 0;
#
# Make a device-dependant object for each switch
#
foreach my $devicename (@devicenames) {
print("Making device object for $devicename\n") if $self->{DEBUG};
my $type = getDeviceType($devicename);
......@@ -93,8 +101,7 @@ sub new($$$@) {
# we weren't given devicenames for devices that aren't switches.
#
SWITCH: for ($type) {
(/cisco65\d\d/ || /cisco40\d\d/ || /cisco45\d\d/ || /cisco29\d\d/ || /cisco55\d\d/)
&& do {
(/cisco/) && do {
use snmpit_cisco;
$device = new snmpit_cisco($devicename,$self->{DEBUG});
last;
......@@ -113,14 +120,17 @@ sub new($$$@) {
}; # /nortel.*/
die "Device $devicename is not of a known type, skipping\n";
}
unless ($device) {
warn "WARNING: Couldn't create device object for $devicename\n";
next;
}
$self->{DEVICES}{$devicename} = $device;
if ($devicename eq $self->{LEADERNAME}) {
$self->{LEADER} = $device;
}
# indented to minimize diffs
if (!$device) {
die "Failed to create a device object for $devicename\n";
} else {
$self->{DEVICES}{$devicename} = $device;
if ($devicename eq $self->{LEADERNAME}) {
$self->{LEADER} = $device;
}
}
if (defined($device->{MIN_VLAN}) &&
($self->{MIN_VLAN} < $device->{MIN_VLAN}))
{ $self->{MIN_VLAN} = $device->{MIN_VLAN}; }
......@@ -131,23 +141,10 @@ sub new($$$@) {
}
bless($self,$class);
return $self;
}
sub lock($) {
my $self = shift;
my $stackid = $self->{STACKID};
my $token = "snmpit_$stackid";
my $old_umask = umask(0);
die if (TBScriptLock($token) != TBSCRIPTLOCK_OKAY());
umask($old_umask);
}
sub unlock($) {
TBScriptUnlock();
return $self;
}
#
# List all VLANs on all switches in the stack
#
......@@ -163,7 +160,7 @@ sub listVlans($) {
my $self = shift;
#
# We need to 'coallate' the results from each switch by putting together
# We need to 'collate' the results from each switch by putting together
# the results from each switch, based on the VLAN identifier
#
my %vlans = ();
......@@ -204,7 +201,7 @@ sub listPorts($) {
my $self = shift;
#
# All we really need to do here is coallate the results of listing the
# All we really need to do here is collate the results of listing the
# ports on all devices
#
my %portinfo = ();
......@@ -222,7 +219,6 @@ sub listPorts($) {
return map $portinfo{$_}, sort {tbsort($a,$b)} keys %portinfo;
}
#
# Puts ports in the VLAN with the given identifier. Contacts the device
# appropriate for each port.
#
......@@ -235,13 +231,11 @@ sub setPortVlan($$@) {
my @ports = @_;
my $errors = 0;
my %mapping = $self->findVlans();
my %map;
#
# Grab the VLAN number
#
my $vlan_number = $mapping{$vlan_id};
my $vlan_number = $self->findVlan($vlan_id);
if (!$vlan_number) {
print STDERR "ERROR: VLAN with identifier $vlan_id does not exist!\n";
return 1;
......@@ -250,7 +244,86 @@ sub setPortVlan($$@) {
#
# Split up the ports among the devices involved
#
%map = mapPortsToDevices(@ports);
my %map = mapPortsToDevices(@ports);
my %trunks = getTrunks();
my @trunks;
if (1) {
#
# Use this hash like a set to find out what switches might be involved
# in this VLAN
#
my %switches;
foreach my $switch (keys %map) {
$switches{$switch} = 1;
}
#
# Find out which ports are already in this VLAN - we might be adding
# to an existing VLAN
# TODO - provide a way to bypass this check for new VLANs, to speed
# things up a bit
#
foreach my $switch ($self->switchesWithPortsInVlan($vlan_number)) {
$switches{$switch} = 1;
}
#
# Find out every switch which might have to transit this VLAN through
# its trunks
#
@trunks = getTrunksFromSwitches(\%trunks, keys %switches);
foreach my $trunk (@trunks) {
my ($src,$dst) = @$trunk;
$switches{$src} = $switches{$dst} = 1;
}
#
# Create the VLAN (if it doesn't exist) on every switch involved
#
foreach my $switch (keys %switches) {
#
# Check to see if the VLAN already exists on this switch
#
my $dev = $self->{DEVICES}{$switch};
if (!$dev) {
warn "ERROR: VLAN uses switch $switch, which is not in ".
"this stack\n";
$errors++;
next;
}
if ($dev->vlanNumberExists($vlan_number)) {
if ($self->{DEBUG}) {
print "Vlan $vlan_id already exists on $switch\n";
}
} else {
#
# Check to see if we had any special arguments saved up for
# this VLAN
#
my @otherargs = ();
if (exists $self->{VLAN_SPECIALARGS}{$vlan_id}) {
@otherargs = @{$self->{VLAN_SPECIALARGS}{$vlan_id}}
}
#
# Create the VLAN
#
my $res = $dev->createVlan($vlan_id,$vlan_number);
if ($res == 0) {
warn "Error: Failed to create VLAN $vlan_id ($vlan_number)".
" on $switch\n";
$errors++;
next;
}
}
}
}
my %BumpedVlans = ();
#
# Perform the operation on each switch
#
foreach my $devicename (keys %map) {
my $device = $self->{DEVICES}{$devicename};
if (!defined($device)) {
......@@ -259,17 +332,46 @@ sub setPortVlan($$@) {
$errors++;
next;
}
#
# We might be adding ports to a switch which doesn't have this
# VLAN yet . . .
# Simply make the appropriate call on the device
#
if (!($device->findVlan($vlan_id)))
{ $errors += !($device->createVlan($vlan_id,$vlan_number)); }
$errors += $device->setPortVlan($vlan_number,@{$map{$devicename}});
#
# When making firewalls, may have to flush FDB entries from trunks
#
if (defined($device->{DISPLACED_VLANS})) {
foreach my $vlan (@{$device->{DISPLACED_VLANS}}) {
$BumpedVlans{$vlan} = 1;
}
$device->{DISPLACED_VLANS} = undef;
}
}
$errors += (!$self->setVlanOnTrunks($vlan_number,1,@ports));
if ($vlan_id ne 'default') {
$errors += (!$self->setVlanOnTrunks2($vlan_number,1,\%trunks,@trunks));
}
#
# When making firewalls, may have to flush FDB entries from trunks
#
foreach my $vlan (keys %BumpedVlans) {
foreach my $devicename ($self->switchesWithPortsInVlan($vlan)) {
my $dev = $self->{DEVICES}{$devicename};
foreach my $neighbor (keys %{$trunks{$devicename}}) {
my $trunkIndex = $dev->getChannelIfIndex(
@{$trunks{$devicename}{neighbor}});
if (!defined($trunkIndex)) {
warn "unable to find channel information on $devicename ".
"for $devicename-$neighbor EtherChannel\n";
$errors += 1;
} else {
$dev->resetVlansIfOnTrunk($trunkIndex,$vlan);
}
}
}
}
return $errors;
}
......@@ -378,8 +480,9 @@ sub findVlans($@) {
my ($count, $device, $devicename) = (scalar(@vlan_ids));
my %mapping = ();
$self->debug("snmpit_stack::findVlans( @vlan_ids )\n",2);
$self->debug("snmpit_stack::findVlans( @vlan_ids )\n");
foreach $devicename (sort {tbsort($a,$b)} keys %{$self->{DEVICES}}) {
$self->debug("stack::findVlans calling $devicename\n");
$device = $self->{DEVICES}->{$devicename};
my %dev_map = $device->findVlans(@vlan_ids);
my ($id,$num,$oldnum);
......@@ -391,12 +494,35 @@ sub findVlans($@) {
} else
{ $mapping{$id} = $num; }
}
if (($count > 0) && ($count = scalar (keys %mapping)))
{ return %mapping ;}
# if (($count > 0) && ($count == scalar (values %mapping))) {
# my @k = keys %mapping; my @v = values %mapping;
# $self->debug("snmpit_stack::findVlans would bail here"
# . " k = ( @k ) , v = ( @v )\n");
# }
}
return %mapping;
}
#
# Given a single VLAN indentifier, find the 802.1Q VLAN tag for it.
#
# usage: findVlan($self, $vlan_id)
# returns the number if found
# 0 otherwise;
#
sub findVlan($$) {
my ($self, $vlan_id) = @_;
$self->debug("snmpit_stack::findVlan( $vlan_id )\n");
foreach my $devicename (sort {tbsort($a,$b)} keys %{$self->{DEVICES}}) {
my $device = $self->{DEVICES}->{$devicename};
my %dev_map = $device->findVlans($vlan_id);
my $vlan_num = $dev_map{$vlan_id};
if (defined($vlan_num)) { return $vlan_num; }
}
return 0;
}
#
# Check to see if the given VLAN exists in the stack
#
......@@ -551,7 +677,7 @@ sub getStats($) {
my $self = shift;
#
# All we really need to do here is coallate the results of listing the
# All we really need to do here is collate the results of listing the
# ports on all devices
#
my %stats = ();
......@@ -581,32 +707,35 @@ sub enableTrunking($$@) {
my @vlan_ids = @_;
#
# On a Cisco, the first VLAN given becomes the native VLAN for the trunk
# Split up the ports among the devices involved
#
my %map = mapPortsToDevices($port);