Commit e05758e4 authored by Kirk Webb's avatar Kirk Webb

Add portchannel support to the snmpit_force10 module

This enhancement uses the new force10_expect module to add/remove
port channels to/from vlans.  Having to do this via expect is
wretched.
parent 4248aef0
...@@ -40,7 +40,7 @@ LIB_STUFF = portstats snmpit_intel.pm \ ...@@ -40,7 +40,7 @@ LIB_STUFF = portstats snmpit_intel.pm \
snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \ snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \
snmpit_arista.pm snmpit_arista_switch_daemon.py \ snmpit_arista.pm snmpit_arista_switch_daemon.py \
snmpit_mellanox.pm MLNX_XMLGateway.pm \ snmpit_mellanox.pm MLNX_XMLGateway.pm \
snmpit_force10.pm snmpit_force10.pm force10_expect.pm
# #
# Force dependencies on the scripts so that they will be rerun through # Force dependencies on the scripts so that they will be rerun through
......
...@@ -52,7 +52,7 @@ sub new($$$$) { ...@@ -52,7 +52,7 @@ sub new($$$$) {
my $name = shift; my $name = shift;
my $debugLevel = shift; my $debugLevel = shift;
my $password = shift; # the password for ssh my $userpass = shift; # username and password
# #
# Create the actual object # Create the actual object
...@@ -69,7 +69,11 @@ sub new($$$$) { ...@@ -69,7 +69,11 @@ sub new($$$$) {
} }
$self->{NAME} = $name; $self->{NAME} = $name;
$self->{PASSWORD} = $password; ($self->{USERNAME}, $self->{PASSWORD}) = split(/:/, $userpass);
if (!$self->{USERNAME} || !$self->{PASSWORD}) {
warn "force10_expect: ERROR: must pass in username AND password!\n";
return undef;
}
if ($self->{DEBUG}) { if ($self->{DEBUG}) {
print "force10_expect initializing for $self->{NAME}, " . print "force10_expect initializing for $self->{NAME}, " .
...@@ -99,7 +103,7 @@ sub createExpectObject($) ...@@ -99,7 +103,7 @@ sub createExpectObject($)
my $self = shift; my $self = shift;
my $id = "$self->{NAME}::createExpectObject()"; my $id = "$self->{NAME}::createExpectObject()";
my $error = 0; my $error = 0;
my $spawn_cmd = "ssh -l admin $self->{NAME}"; my $spawn_cmd = "ssh -l $self->{USERNAME} $self->{NAME}";
# Create Expect object and initialize it: # Create Expect object and initialize it:
my $exp = new Expect(); my $exp = new Expect();
if (!$exp) { if (!$exp) {
...@@ -119,9 +123,10 @@ sub createExpectObject($) ...@@ -119,9 +123,10 @@ sub createExpectObject($)
return undef; return undef;
} }
$exp->expect($CONN_TIMEOUT, $exp->expect($CONN_TIMEOUT,
["admin\@$self->{NAME}'s password:" => sub { my $e = shift; ["$self->{USERNAME}\@$self->{NAME}'s password:" =>
$e->send($self->{PASSWORD}."\n"); sub { my $e = shift;
exp_continue;}], $e->send($self->{PASSWORD}."\n");
exp_continue;}],
["Permission denied" => sub { $error = "Password incorrect!";} ], ["Permission denied" => sub { $error = "Password incorrect!";} ],
[ timeout => sub { $error = "Timeout connecting to switch!";} ], [ timeout => sub { $error = "Timeout connecting to switch!";} ],
$self->{CLI_PROMPT} ); $self->{CLI_PROMPT} );
......
...@@ -39,6 +39,7 @@ use English; ...@@ -39,6 +39,7 @@ use English;
use SNMP; use SNMP;
use snmpit_lib; use snmpit_lib;
use Port; use Port;
use force10_expect;
# #
# These are the commands that can be passed to the portControl function # These are the commands that can be passed to the portControl function
...@@ -90,6 +91,10 @@ my $ChassisInfo = { ...@@ -90,6 +91,10 @@ my $ChassisInfo = {
# #
my $vlanStaticNamePrefix = "emuID"; my $vlanStaticNamePrefix = "emuID";
# Enterprise OIDs for port aggregations
my $OID_AGGINDEX = ".1.3.6.1.4.1.6027.3.2.1.1.1.1.3";
my $OID_AGGPLIST = ".1.3.6.1.4.1.6027.3.2.1.1.1.1.6";
# #
# Ports can be passed around in three formats: # Ports can be passed around in three formats:
# ifindex : positive integer corresponding to the interface index # ifindex : positive integer corresponding to the interface index
...@@ -171,6 +176,7 @@ sub new($$$;$) { ...@@ -171,6 +176,7 @@ sub new($$$;$) {
# #
$self->{IFINDEX} = {}; # Used for converting modport to ifIndex (somtimes called iid; e.g. 345555002) and vice versa $self->{IFINDEX} = {}; # Used for converting modport to ifIndex (somtimes called iid; e.g. 345555002) and vice versa
$self->{PORTINDEX} = {}; # Will contain elements for experimental "interfaces" only (no VLANs, no Mgmgnt ifaces); format: ifIndex => ifDescr $self->{PORTINDEX} = {}; # Will contain elements for experimental "interfaces" only (no VLANs, no Mgmgnt ifaces); format: ifIndex => ifDescr
$self->{POIFINDEX} = {}; # Port channel ifindex map.
if ($self->{DEBUG}) { if ($self->{DEBUG}) {
print "snmpit_force10 module initializing... debug level $self->{DEBUG}\n"; print "snmpit_force10 module initializing... debug level $self->{DEBUG}\n";
...@@ -210,12 +216,26 @@ sub new($$$;$) { ...@@ -210,12 +216,26 @@ sub new($$$;$) {
return undef; return undef;
} }
# Grab our expect object (Ugh... Why Force10, why?)
# This does not immediately connect to the switch.
if (exists($options{"username"}) && exists($options{"password"})) {
my $swcreds = $options{"username"} . ":" . $options{"password"};
$self->{EXP_OBJ} = force10_expect->new($self->{NAME},$debugLevel,
$swcreds);
if (!$self->{EXP_OBJ}) {
warn "Could not create Expect object for $self->{NAME}\n";
return undef;
}
} else {
warn "WARNING: No credentials found for force10 switch $self->{NAME}\n";
warn "\tPortchannel manipulation will not be possible!\n";
}
# #
# Connecting an SNMP session doesn't necessarily mean you can actually get # Connecting an SNMP session doesn't necessarily mean you can actually get
# packets to and from the switch. Test that by grabbing an OID that should # packets to and from the switch. Test that by grabbing an OID that should
# be on every switch. Let it retry a bunch, to hide transient failures # be on every switch. Let it retry a bunch, to hide transient failures
# #
my $OS_details = snmpitGetFatal($self->{SESS},["sysDescr",0],30); my $OS_details = snmpitGetFatal($self->{SESS},["sysDescr",0],30);
print "Switch $self->{NAME} is running $OS_details\n" if $self->{DEBUG}; print "Switch $self->{NAME} is running $OS_details\n" if $self->{DEBUG};
...@@ -302,6 +322,13 @@ sub readifIndex($) { ...@@ -302,6 +322,13 @@ sub readifIndex($) {
$self->debug("mod $module, port $port, modport $modport, descr $descr\n",2); $self->debug("mod $module, port $port, modport $modport, descr $descr\n",2);
} }
} }
elsif ($descr =~ /port-channel (\d+)/i) {
my $ifIndex = $iid;
my $poNum = $1;
$self->{POIFINDEX}{$ifIndex} = "Po$poNum";
$self->{POIFINDEX}{"Po$poNum"} = $ifIndex;
$self->debug("Port-channel $poNum, ifindex $ifIndex\n",2);
}
} }
# success # success
...@@ -1678,38 +1705,85 @@ sub disablePortTrunking($$) { ...@@ -1678,38 +1705,85 @@ sub disablePortTrunking($$) {
# #
# usage: getChannelIfIndex(self, ports) # usage: getChannelIfIndex(self, ports)
# Returns: undef if more than one port is given, and no channel is found # 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 # an ifindex if a channel is found and/or only one port is given.
#
# XXX: incomplete: will not find an ifindex for a portchannel. This is
# good since we can't yet configure portchannels (no snmp support in
# FTOS). So, only works on single-wire inter-switch trunks.
# #
sub getChannelIfIndex($@) { sub getChannelIfIndex($@) {
my $self = shift; my $self = shift;
my @ports = @_; my @ports = @_;
my @ifIndexes = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports); my @modports = $self->convertPortFormat($PORT_FORMAT_MODPORT,@ports);
my $ifindex = undef; my $ifindex = undef;
my $id = "$self->{NAME}::getChannelIfIndex()"; my $id = "$self->{NAME}::getChannelIfIndex()";
$self->debug("$id: entering ".join(",",@ports)."\n"); $self->debug("$id: entering ".join(",",@ports)."\n");
return undef return undef
if (! @ifIndexes); if (! @ifIndexes);
$self->debug("$id: ".join(",",@ifIndexes)."\n"); $self->debug("$id: ".join(",",@modports)."\n");
my ($rows) = snmpitBulkwalkFatal($self->{SESS},[$OID_AGGPLIST]);
# Try to find the input ports in the membership lists of the port
# channels on this switch. Stop and return the first match found.
if ($rows) {
RESULTS: foreach my $result (@{$rows}) {
my ($name,$channelid,$members) = @{$result};
$self->debug("$id: got $name, $channelid, members $members\n",2);
# Chop up membership list into individual members. The stupid
# thing is returned as a string that looks like this:
# "Fo 0/52 Fo 0/56 ...". So, we must hop over the port type
# identifiers, and grab the port numbers.
my @elements = split(/\s+/, $members);
for (my $i = 0; $i < @elements; $i += 2) {
my $membmodport = join(".", split(/\//, $elements[$i+1]));
foreach my $modport (@modports) {
if ($modport eq $membmodport &&
exists{$self->{POIFINDEX}{"Po$channelid"}}) {
$ifindex = $self->{POIFINDEX}{"Po$channelid"};
last RESULTS;
}
}
}
}
}
# # If we don't yet have a portchannel index, and a single port was
# Try to get a channel number for each one of the ports in turn - we'll # passed in, just return the ifindex for that port (single wire trunks).
# take the first one we get if (!$ifindex && scalar(@ports) == 1) {
# if (exists($self->{IFINDEX}{$ports[0]})) {
foreach my $port (@ifIndexes) { $ifindex = $self->{IFINDEX}{$port[0]};
if ($port) { $ifindex = $port; last; } }
} }
$self->debug("$id: $ifindex\n"); $self->debug("$id: $ifindex\n");
return $ifindex; return $ifindex;
} }
#
# Helper function that calls the Expect CLI wrapper for this switch to
# add/remove a portchannel to/from a vlan. That this task cannot be
# carried out via a programmatic API (snmp or other) is terrible.
#
sub setChannelVlan($$$;$) {
my ($self, $vlanid, $poifindex, $remove) = @_;
$remove ||= 0;
my $id = "$self->{NAME}::setChannelVlan";
if (!exists($self->{POIFINDEX}{$poifindex})) {
warn "$id: $poifindex does not exist in portchannel ifindex map!\n";
return 1;
}
my $poname = $self->{POIFINDEX}{$poifindex};
my $cmd = $remove ? "no tagged $poname" : "tagged $poname";
my $isconfig = 1;
my $vlifname = "vlan$vlanid";
my ($res, $out) = $self->{EXP_OBJ}->doCLICmd($cmd, $isconfig, $vlifname);
if (!$res) {
warn "$id: Error adding vlan to channel: $out\n";
}
return $res;
}
# #
# Enable, or disable, port on a trunk # Enable, or disable, port on a trunk
...@@ -1720,16 +1794,19 @@ sub getChannelIfIndex($@) { ...@@ -1720,16 +1794,19 @@ sub getChannelIfIndex($@) {
# vlan_numbers: An array of 802.1Q VLAN numbers to operate on # vlan_numbers: An array of 802.1Q VLAN numbers to operate on
# Returns 1 on success, 0 otherwise # Returns 1 on success, 0 otherwise
# #
# XXX: incomplete: does not operate on portchannels, only single-wire trunks.
#
sub setVlansOnTrunk($$$$) { sub setVlansOnTrunk($$$$) {
my ($self, $modport, $value, @vlan_numbers) = @_; my ($self, $modport, $value, @vlan_numbers) = @_;
my ($RetVal);
my $errors = 0; my $errors = 0;
my $id = $self->{NAME} . "::setVlansOnTrunk"; my $id = $self->{NAME} . "::setVlansOnTrunk";
$self->debug("$id: entering, modport: $modport, value: $value, vlans: ".join(",",@vlan_numbers)."\n"); $self->debug("$id: entering, modport: $modport, value: $value, vlans: ".join(",",@vlan_numbers)."\n");
my ($ifindex) = $self->convertPortFormat(PORT_FORMAT_IFINDEX, $modport);
if (!$ifindex) {
warn "$id: WARNING: Could not get ifindex for port $modport\n";
return 0;
}
# #
# Some error checking (from HP) # Some error checking (from HP)
# #
...@@ -1742,18 +1819,29 @@ sub setVlansOnTrunk($$$$) { ...@@ -1742,18 +1819,29 @@ sub setVlansOnTrunk($$$$) {
return 0; return 0;
} }
# Add or remove the vlan from the trunk on the port or portchannel.
# Different code is called to manipulate the trunk depending on whether
# the interface is a regular port or a port channel.
foreach my $vlan (@vlan_numbers) { foreach my $vlan (@vlan_numbers) {
if ($value == 1) { if ($value == 1) {
$errors += $self->setPortVlan($vlan, $modport); if (exists($self->{POIFINDEX}{$ifindex})) {
$errors += $self->setChannelVlan($vlan, $ifindex);
} else {
$errors += $self->setPortVlan($vlan, $ifindex);
}
} else { } else {
$errors += $self->removeSomePortsFromVlan($vlan, $modport); if (exists($self->{POIFINDEX}{$ifindex})) {
my $remove = 1;
$errors += $self->setChannelVlan($vlan, $ifindex, $remove);
} else {
$errors += $self->removeSomePortsFromVlan($vlan, $ifindex);
}
} }
} }
return $errors ? 0 : 1; return $errors ? 0 : 1;
} }
# #
# Used to flush FDB entries easily # Used to flush FDB entries easily
# #
...@@ -1761,7 +1849,6 @@ sub setVlansOnTrunk($$$$) { ...@@ -1761,7 +1849,6 @@ sub setVlansOnTrunk($$$$) {
# #
sub resetVlanIfOnTrunk($$$) { sub resetVlanIfOnTrunk($$$) {
my ($self, $modport, $vlan) = @_; my ($self, $modport, $vlan) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
my $id = $self->{NAME} . "::resetVlansOnTrunk"; my $id = $self->{NAME} . "::resetVlansOnTrunk";
$self->debug("$id: entering, modport: $modport, vlan: $vlan\n"); $self->debug("$id: entering, modport: $modport, vlan: $vlan\n");
......
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