Commit 223fff16 authored by Robert Ricci's avatar Robert Ricci
Browse files

Intel support is now fully functional. This mostly invovled making the

snmpit_intel module conform to the same API as snmpit_cisco.

Intels VLANs are now done per port rather than per MAC. This should give
experimenters more flexibility on the experimental net, and is more consistent
with the way that VLANs are done on other switches.

snmpit_intel_stack will need to undergo minor work to support stacks of
multiple switches.
parent c27dcd5e
......@@ -24,13 +24,17 @@ use snmpit_lib;
#
my %cmdOIDs =
(
"enable" => [".1.3.6.1.2.1.2.2.1.7","up"],
"disable"=> [".1.3.6.1.2.1.2.2.1.7","down"],
"100mbit"=> [".1.3.6.1.4.1.343.6.10.2.4.1.10.1.1","speed100Mbit"],
"10mbit" => [".1.3.6.1.4.1.343.6.10.2.4.1.10.1.1","speed10Mbit"],
"full" => [".1.3.6.1.4.1.343.6.10.2.4.1.11.1.1","full"],
"half" => [".1.3.6.1.4.1.343.6.10.2.4.1.11.1.1","half"],
"auto" => [".1.3.6.1.4.1.343.6.10.2.4.1.12.1.1","auto"]
"enable" => ["ifAdminStatus","up"],
"disable"=> ["ifAdminStatus","down"],
"100mbit"=> ["portConfConfigAutoNeg","disable",
"portConfConfigSpeed","speed100Mbit"],
"10mbit" => ["portConfConfigAutoNeg","disable",
"portConfConfigSpeed","speed10Mbit"],
"full" => ["portConfConfigAutoNeg","disable",
"portConfConfigDuplex","full"],
"half" => ["portConfConfigAutoNeg","disable",
"portConfConfigDuplex","half"],
"auto" => ["portConfConfigAutoNeg","enable"]
);
......@@ -63,13 +67,22 @@ sub new {
$self->{DEBUG} = 0;
}
$self->{BLOCK} = 1;
$self->{CONFIRM} = 1;
$self->{NAME} = $name;
if ($self->{DEBUG}) {
print "snmpit_cisco module initializing... debug level $self->{DEBUG}\n"
print "snmpit_intel module initializing... debug level $self->{DEBUG}\n"
;
}
#
# This variable is used to determined how deep in locking the VLAN edit
# buffer we are. This is so that we can have 'nested' calls to vlanLock,
# and only the first one will grab the lock from the switch, and only
# the last call to vlanUnlock will unlock on the switch.
#
$self->{LOCKLEVEL} = 0;
#
# Set up SNMP module variables, and connect to the device
#
......@@ -85,7 +98,6 @@ sub new {
warn ("Opening SNMP session to $self->{NAME}...") if ($self->{DEBUG});
# XXX: SNMP version?
$self->{SESS} = new SNMP::Session(DestHost => $self->{NAME});
if (!$self->{SESS}) {
......@@ -117,15 +129,23 @@ sub portControl($$@) {
$self->debug("portControl: $cmd -> (@ports)\n");
# XXX: portnum
my @p = map { portnum($_) } @ports;
#
# Find the command in the %cmdOIDs hash (defined at the top of this file)
# Find the command in the %cmdOIDs hash (defined at the top of this file).
# Some commands involve multiple SNMP commands, so we need to make sure
# we get all of them
#
my @oid = @{$cmdOIDs{$cmd}};
if (defined @oid) {
return &UpdateField(\$oid[0],\$oid[1],\@p);
if (defined $cmdOIDs{$cmd}) {
my @oid = @{$cmdOIDs{$cmd}};
my $errors = 0;
while (@oid) {
my $myoid = shift @oid;
my $myval = shift @oid;
$errors += $self->UpdateField($myoid,$myval,@ports);
}
return $errors;
} else {
#
# Command not supported
......@@ -135,6 +155,15 @@ sub portControl($$@) {
}
}
#
# Obtain a lock on the VLAN edit buffer. This must be done before VLANs
# are created or removed, or ports are assigned to VLANs. Will retry 20
# times before failing
#
# usage: vlanLock($self)
# returns 1 on success
# returns 0 on failure
#
sub vlanLock($) {
my $self = shift;
......@@ -144,6 +173,17 @@ sub vlanLock($) {
my $TokenRelease = '.1.3.6.1.4.1.343.6.11.4.8';
my $TokenReleaseResult = '.1.3.6.1.4.1.343.6.11.4.9';
#
# For nested locks - if we're called, but already locked, we just
# report sucess and increase the lock level
#
if ($self->{LOCKLEVEL}) {
$self->debug("vlanLock - already at lock level $self->{LOCKLEVEL}\n");
$self->{LOCKLEVEL}++;
return 1;
}
$self->{LOCKLEVEL} = 1;
#
# Some magic needed by the Intel switch
#
......@@ -200,7 +240,9 @@ sub vlanLock($) {
#
# Release a lock on the VLAN edit buffer. As part of releasing, applies the
# VLAN edit buffer.
# VLAN edit buffer. If $self->{CONFIRM} is set, uses the saveWithConfirmOption,
# which requires the porcess to confirm the changes, or they are cancelled
# (so that you don't accidentally cut yourself off from the switch.)
#
# usage: vlanUnlock($self)
#
......@@ -211,13 +253,30 @@ sub vlanUnlock($;$) {
my $TokenReleaseResult = '.1.3.6.1.4.1.343.6.11.4.9';
my $TokenConfirmState = '.1.3.6.1.4.1.343.6.11.1.18';
#
# Allow for nested locks - only release the lock if our locklevel
# is down to 1 (ie. we're the last level that cares about the lock)
#
if ($self->{LOCKLEVEL} > 1) {
$self->debug("vlanUnock - at lock level $self->{LOCKLEVEL}\n");
$self->{LOCKLEVEL}--;
return 1;
}
$self->{LOCKLEVEL} = 0;
my $save = ($self->{CONFIRM} ? "saveWithConfirmOption" : "save");
#
# Relse the token
#
$self->debug("Releasing Token with $save command\n");
$self->{SESS}->set([[$TokenRelease,0,$save,"INTEGER"]]);
my $RetVal = $self->{SESS}->get([[$TokenReleaseResult,0]]);
$self->debug("VLAN Configuration Save Result is $RetVal\n");
#
# Wait for the switch to report that the token has been released
#
while ($RetVal eq "notReady") {
$RetVal = $self->{SESS}->get([[$TokenReleaseResult,0]]);
$self->debug("VLAN Configuration Save Result is $RetVal\n");
......@@ -225,86 +284,404 @@ sub vlanUnlock($;$) {
}
if ($self->{CONFIRM}) {
#
# Wait for the switch to report that it's ready to recieve
# confirmation of the release
#
$RetVal = $self->{SESS}->get([[$TokenConfirmState,0]]);
$self->debug("VLAN Configuration Confirm Result is $RetVal\n");
while ($RetVal eq "notReady") {
sleep(2);
$RetVal = $self->{SESS}->get([[$TokenConfirmState,0]]);
$self->debug("VLAN Configuration Confirm Result is $RetVal\n");
}
#
# Confirm the release
#
if ($RetVal eq "ready") {
$RetVal = $self->{SESS}->set([[$TokenConfirmState,0,"confirm","INTEGER"]]);
$RetVal = $self->{SESS}->set([[$TokenConfirmState,0,"confirm",
"INTEGER"]]);
$self->debug("VLAN Configuration Confirm Result is $RetVal\n");
} # XXX: Should there be an 'else'?
#
# Wait for the switch to confirm the confirmation
#
while (!($RetVal =~ /Conf/i)) {
$RetVal = $self->{SESS}->get([[$TokenConfirmState,0]]);
$self->debug("VLAN Configuration Confirm Result is $RetVal\n");
}
#
# Make sure the confirmation was confirmed
#
if ($RetVal ne "confirmedNewConf") {
die("VLAN Reconfiguration Failed. No changes saved.\n");
}
} else {
} else { # if $self->{CONFIRM}
#
# Just check the return value
#
if ($RetVal ne 'success') {
die("VLAN Reconfiguration Failed: $RetVal. No changes saved.\n");
}
}
}
# XXX: Changeme!
#sub setupVlan {
# # This is to be called ONLY after vlanLock has been called successfully!
# my $sess = shift;
# $sess = $$sess; # unreference it...
# my $name = shift;
# my @vlan = @_;
# foreach my $mac (@vlan) {
# my $node = $Interfaces{$mac};
# if (! NodeCheck($node)) {
# print STDERR "You are not authorized to control $node.\n";
# return 1;
# }
# }
#
# my $NextVLANId = '.1.3.6.1.4.1.343.6.11.1.6';
# my $Vlan = $sess->get([[$NextVLANId,0]]);
# my $CreateOID = ".1.3.6.1.4.1.343.6.11.1.9.1.3";
# my $RetVal = "Undef.";
# if ( !$name
# #Temporary fix: if its a lX-Y name from assign.tcl, ignore it
# || ($name =~ /^l(\d)+-(\d)+/)
# #End of Temp fix
# ) {
# $name = $Vlan;
# }
# print " Creating VLAN $name as VLAN #$Vlan: @vlan ... ";
# $RetVal = $sess->set([[$CreateOID,$Vlan,$name,"OCTETSTR"]]);
# print "",($RetVal? "Succeeded":"Failed"),".\n";
# if (! defined ($RetVal) ) {
# &vlanUnlock(*sess,$verbose,1);
# die("VLAN name \"$name\" not unique.\n");
# }
# my @x;
# my $n=0;
# while (@vlan != 0 && $n < @vlan) {
# my $i=0;
# while ($i < 6 ) {
# $x[$i] = hex ("0x".substr($vlan[$n],2*$i,2) );
# $i++;
# }
# my $MacObjOID = ".1.3.6.1.4.1.343.6.11.1.10.1.3.$Vlan." .
# "$x[0].$x[1].$x[2].$x[3].$x[4].$x[5]";
# print " Adding MAC Address $vlan[$n] ".
# "($Vlan.$x[0].$x[1].$x[2].$x[3].$x[4].$x[5])... ";
# $RetVal = $sess->set([[$MacObjOID,0,$vlan[$n],"OCTETSTR"]]);
# print "",($RetVal? "Succeeded":"Failed"), ".\n";
# $n++;
# }
#}
#
# Given a VLAN identifier from the database, find the intel-specific VLAN
# number that is assigned to that VLAN. Retries several times (to account
# for propagation delays) unless the $no_retry option is given.
#
# usage: findVlan($self, $vlan_id,$no_retry)
# returns the VLAN number for the given vlan_id if it exists
# returns undef if the VLAN id is not found
#
sub findVlan($$;$) {
my $self = shift;
my $vlan_id = shift;
my $no_retry = shift;
my $max_tries;
if ($no_retry) {
$max_tries = 1;
} else {
$max_tries = 10;
}
my $field = ["policyVlanName",0];
#
# We try this a few time, with 1 second sleeps, since it can take
# a while for VLAN information to propagate
#
foreach my $try (1 .. $max_tries) {
#
# Do one to get the first field...
#
$self->{SESS}->getnext($field);
my ($name,$vlan_number,$vlan_name);
do {
($name,$vlan_number,$vlan_name) = @{$field};
$self->debug("findVlan: Got $name $vlan_number $vlan_name\n");
#
# We only want the names - we ignore everything else
#
if ($name =~ /policyVlanName/) {
if ($vlan_name eq $vlan_id) {
return $vlan_number;
}
}
$self->{SESS}->getnext($field);
} while ( $name =~ /^policyVlanName/) ;
#
# Wait before we try again
#
if ($try != $max_tries) {
$self->debug("VLAN find failed, trying again\n");
sleep 1;
}
}
#
# Didn't find it
#
return undef;
}
#
# Create a VLAN on this switch, with the given identifier (which comes from
# the database.) Picks its own switch-specific VLAN number to use.
#
# usage: createVlan($self, $vlan_id)
# returns 1 on success
# returns 0 on failure
#
sub createVlan($$) {
my $self = shift;
my $vlan_id = shift;
my $okay = 0;
if (!$self->vlanLock()) {
return 0;
}
my $NextVLANId = '.1.3.6.1.4.1.343.6.11.1.6';
my $CreateOID = ".1.3.6.1.4.1.343.6.11.1.9.1.3";
my $RetVal = "Undef.";
#
# Intel provides us with a handy way to pick a VLAN number
#
my $vlan_number = $self->{SESS}->get([[$NextVLANId,0]]);
#
# Pick a name if one was not given
#
if (!$vlan_id) {
$vlan_id = $vlan_number;
}
print " Creating VLAN $vlan_id as VLAN #$vlan_number ... ";
$RetVal = $self->{SESS}->set([[$CreateOID,$vlan_number,$vlan_id,
"OCTETSTR"]]);
if ($RetVal) {
$okay = 1;
print "Succeeded\n";
} else {
print "Failed\n";
$okay = 0;
}
$self->vlanUnlock();
return 0;
}
#
# Put the given ports in the given VLAN. The VLAN is given as a VLAN
# identifier from the database.
#
# usage: setPortVlan($self,$vlan_id, @ports)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub setPortVlan($$@) {
my $self = shift;
my $vlan_id = shift;
my @ports = @_;
my $errors = 0;
#
# Find the real VLAN number from the passed VLAN ID
#
my $vlan_number = $self->findVlan($vlan_id);
if (!defined($vlan_number)) {
print STDERR "ERROR: VLAN with identifier $vlan_id does not exist\n";
return 1;
}
$self->debug("Found VLAN with ID $vlan_id: $vlan_number\n");
#
# Run the port list through portmap to find the ports on the switch that
# we are concerned with
#
$self->debug("Raw port list: " . join(",",@ports) . "\n");
my @portlist = map { portnum($_) } @ports;
if (!$self->vlanLock()) {
return 1;
}
foreach my $port (@portlist) {
#
# The value returned from portmap looks like: intel1:1.1 . It's
# only the last part, the port number, that we acutally care about
# (since intels don't have multiple cards, every port is considered
# to be on port 1)
#
$port =~ /:1\.(\d+)$/;
$port = $1;
#
# Make sure the port didn't get mangled in conversion
#
if (!defined $port) {
print STDERR "Port not found, skipping\n";
$errors++;
next;
}
$self->debug("Putting port $port in VLAN $vlan_number\n");
#
# Remove the port from all VLANs it's currently in (or we can end
# up with it in multiple VLANs)
#
$self->removePortFromVlans($port);
#
# Do the acutal SNMP command
#
my $policyPortRuleCreateObj = "policyPortRuleCreateObj";
my $RetVal = $self->{SESS}->set([$policyPortRuleCreateObj,
"$vlan_number.$port",$vlan_number,'OCTETSTR']);
if (!$RetVal) {
print STDERR "$port VLAN change failed with $RetVal.\n";
$errors++;
next;
}
#
# Ports going into the System vlan are being taken out of circulation,
# so we disable them. Otherwise, we need to make sure they get enabled.
#
if ($vlan_id eq "System") {
$self->debug("Disabling " . join(',',@ports) . "...");
if ( my $rv = $self->portControl("disable",@ports) ) {
print STDERR "Port disable had $rv failures.\n";
$errors += $rv;
}
} else {
$self->debug("Enabling " . join(',',@ports) . "...");
if ( my $rv = $self->portControl("enable",@ports) ) {
print STDERR "Port enable had $rv failures.\n";
$errors += $rv;
}
}
}
$self->vlanUnlock();
return $errors;
}
#
# Remove a port from all VLANs of which it it currently a member
#
# usage: removePortFromVlans(self,int port)
# returns 0 on sucess.
# returns the number of failed VLANs on failure.
#
sub removePortFromVlans($$) {
my $self = shift;
my $port_number = shift;
my $errors = 0;
$self->debug("removePortFromVlans($port_number) called\n");
my $field = ["policyPortRuleDeleteObj",0];
if (!$self->vlanLock()) {
return 1;
}
#
# Do one to get the first field...
#
$self->{SESS}->getnext($field);
my ($name,$index,$value);
do {
($name,$index,$value) = @{$field};
$self->debug("removePortFromVlans: Got $name $index $value\n");
if ($name eq "policyPortRuleDeleteObj") {
#
# This table is indexed by vlan.port
#
$index =~ /(\d+)\.(\d+)/;
my ($vlan,$port) = ($1,$2);
#
# If this was the port we're looking for, nuke it!
#
if ($port == $port_number) {
my $RetVal = $self->{SESS}->set(["policyPortRuleDeleteObj",
$index,1,"INTEGER"]);
if (!$RetVal) {
$errors++;
}
}
}
$self->{SESS}->getnext($field);
} while ( $name =~ /^policyPortRuleDeleteObj/) ;
$self->vlanUnlock();
return $errors;
}
#
# Remove all ports from the given VLAN. The VLAN is given as a VLAN
# identifier from the database.
#
# usage: removePortsFromVlan(self,int vlan)
# returns 0 on sucess.
# returns the number of failed ports on failure.
#
sub removePortsFromVlan($$) {
my $self = shift;
my $vlan_id = shift;
my $errors = 0;
#
# Find the real VLAN number from the passed VLAN ID
#
my $vlan_number = $self->findVlan($vlan_id);
if (!defined($vlan_number)) {
print STDERR "ERROR: VLAN with identifier $vlan_id does not exist\n";
return 1;
}
$self->debug("Found VLAN with ID $vlan_id: $vlan_number\n");
if (!$self->vlanLock()) {
return 1;
}
my $field = ["policyPortRuleDeleteObj",0];
#
# Do one to get the first field...
#
$self->{SESS}->getnext($field);
my ($name,$index,$value);
do {
($name,$index,$value) = @{$field};
if ($name eq "policyPortRuleDeleteObj") {
$self->debug("removePortsFromVlan: Got $name $index $value\n");
#
# This table is indexed by vlan.port
#
$index =~ /(\d+)\.(\d+)/;
my ($vlan,$port) = ($1,$2);
if ($vlan == $vlan_number) {
#
# Remove the port from this VLAN
#
$self->debug("Removing $port from vlan $vlan\n");
my $RetVal = $self->{SESS}->set(["policyPortRuleDeleteObj",
$index,1,"INTEGER"]);
if ($RetVal) {
$errors++;
}
#
# Put the port in the System VLAN. We have to translate the
# port into node:port form since that's what setPortVlan
# expects.
#
my $portnum = portnum("$self->{NAME}:1.$port");
$self->debug("Calling setPortVlan(System,$portnum)");
my $errors += $self->setPortVlan("System",$portnum);
}
}
$self->{SESS}->getnext($field);
} while ( $name =~ /^policyPortRuleDeleteObj/) ;
$self->vlanUnlock();
return $errors;
}
#
# Remove the given VLAN from this switch. This presupposes that all of its
......@@ -344,7 +721,8 @@ sub removeVlan($$) {
my $RetVal = "Undef.";
print " Removing VLAN #$vlan_number ... ";
$RetVal = $self->{SESS}->set([[$DeleteOID,$vlan_number,"delete","INTEGER"]]);
$RetVal = $self->{SESS}->set([[$DeleteOID,$vlan_number,"delete",
"INTEGER"]]);
print "",($RetVal? "Succeeded":"Failed"),".\n";
#
......@@ -375,8 +753,20 @@ sub UpdateField($$$@) {
$self->debug("UpdateField: '@_'\n");
foreach my $port (@ports) {
#