Commit 4f07dd4f authored by Kirk Webb's avatar Kirk Webb

Merge branch 'snmpit-force10'

parents 28603260 e5d9b3be
......@@ -40,7 +40,7 @@ LIB_STUFF = portstats snmpit_intel.pm \
snmpit_nortel.pm snmpit_hp.pm snmpit_apcon.pm \
snmpit_arista.pm snmpit_arista_switch_daemon.py \
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
......
#!/usr/bin/perl -w
# Force10 Expect wrapper testing harness.
use strict;
use English;
use Getopt::Std;
use force10_expect;
my @get_test1 = (
["name", "Basic command execution test #1"],
["cmd", "show version | no-more"],
["cmd", "show vlan | no-more"],
["cmd", "badcmd"],
["cmd", "badagain"],
["cmd", "show hosts"],
);
my @timeout_test1 = (
["cmd", "show system"]
);
my @config_test1 = (
["config", "snmp-server contact Kirk Webb"],
["config", "snmp-server location DDC"],
["config", "derp-derp"]
);
my @iface_config_test1 = (
["ifconfig", "name testing_lan", "vlan666"],
["ifconfig", "derp", "vlan666"]
);
# List the tests to run here.
my @testsets = (\@get_test1,\@config_test1,\@iface_config_test1);
my %opts = ();
if (!getopts("n:p:d:",\%opts)) {
print "Usage: $0 -n <switch_name> -p <password> -d <level>\n";
exit 1;
}
my $switch = "";
my $pass = "";
my $debug = 0;
$switch = $opts{'n'} or die "Must specify switch name!";
$pass = $opts{'p'} or die "Must specify password!";
$debug = $opts{'d'} || 0;
my $wrapper = force10_expect->new($switch, $debug, $pass);
foreach my $tlist (@testsets) {
my @results = ();
my $testname = "unnamed";
foreach my $cmd (@{$tlist}) {
TESTSW1: for ((@{$cmd})[0]) {
/^name$/ && do {
$testname = (@{$cmd})[1];
print "========== Running Test: $testname ==========\n";
last TESTSW1;
};
/^cmd$/ && do {
push @results, ($wrapper->doCLICmd((@{$cmd})[1]))[1];
last TESTSW1;
};
/^config$/ && do {
push @results, ($wrapper->doCLICmd((@{$cmd})[1],1))[1];
last TESTSW1;
};
/^ifconfig$/ && do {
push @results, ($wrapper->doCLICmd((@{$cmd})[1],1,(@{$cmd})[2]))[1];
last TESTSW1;
};
# Default
print "Error: Unknown command: $_\n";
}
}
print "--- Results:\n";
my $i = 1;
foreach my $resstr (@results) {
print "*** Submission $i: $resstr\n\n";
++$i;
}
}
#!/usr/bin/perl -w
#
# Copyright (c) 2013,2014 University of Utah and the Flux Group.
# Copyright (c) 2006-2014 Universiteit Gent/iMinds, Belgium.
# Copyright (c) 2004-2006 Regents, University of California.
#
# {{{EMULAB-LGPL
#
# This file is part of the Emulab network testbed software.
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
#
# Expect module for Force10 switch cli interaction. A thousand curses to
# Force10 Networks that this module had to be written at all...
#
package force10_expect;
use strict;
use Data::Dumper;
$| = 1; # Turn off line buffering on output
use English;
use Expect;
# Constants
my $CONN_TIMEOUT = 60;
my $CLI_TIMEOUT = 15;
my $DEBUG_LOG = "/tmp/force10_expect_debug.log";
sub new($$$$) {
# The next two lines are some voodoo taken from perltoot(1)
my $proto = shift;
my $class = ref($proto) || $proto;
my $name = shift;
my $debugLevel = shift;
my $userpass = shift; # username and password
#
# Create the actual object
#
my $self = {};
#
# Set the defaults for this object
#
if (defined($debugLevel)) {
$self->{DEBUG} = $debugLevel;
} else {
$self->{DEBUG} = 0;
}
$self->{NAME} = $name;
($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}) {
print "force10_expect initializing for $self->{NAME}, " .
"debug level $self->{DEBUG}\n" ;
}
$self->{CLI_PROMPT} = "$self->{NAME}#";
# Make it a class object
bless($self, $class);
#
# Lazy initialization of the Expect object is adopted, so
# we set the session object to be undef.
#
$self->{SESS} = undef;
return $self;
}
#
# Create an Expect object that spawns the ssh process
# to switch.
#
sub createExpectObject($)
{
my $self = shift;
my $id = "$self->{NAME}::createExpectObject()";
my $error = 0;
my $spawn_cmd = "ssh -l $self->{USERNAME} $self->{NAME}";
# Create Expect object and initialize it:
my $exp = new Expect();
if (!$exp) {
# upper layer will check this
return undef;
}
$exp->raw_pty(0);
$exp->log_stdout(0);
if ($self->{DEBUG} > 1) {
$exp->log_file($DEBUG_LOG,"w");
$exp->debug(1);
}
if (!$exp->spawn($spawn_cmd)) {
warn "$id: Cannot spawn $spawn_cmd: $!\n";
return undef;
}
$exp->expect($CONN_TIMEOUT,
["$self->{USERNAME}\@$self->{NAME}'s password:" =>
sub { my $e = shift;
$e->send($self->{PASSWORD}."\n");
exp_continue;}],
["Permission denied" => sub { $error = "Password incorrect!";} ],
[ timeout => sub { $error = "Timeout connecting to switch!";} ],
$self->{CLI_PROMPT} );
if (!$error && $exp->error()) {
$error = $exp->error();
}
if ($error) {
warn "$id: Could not connect to switch: $error\n";
return undef;
}
return $exp;
}
#
# Utility function - return the configuration prompt string for a given
# interface name.
#
sub conf_iface_prompt($$) {
my ($self, $iface) = @_;
my $suffix = "";
IFNAME: for ($iface) {
/vlan(\d+)/i && do {$suffix = "vl-$1"; last IFNAME;};
/(te|fo)(\d+\/\d+)/i && do {$suffix = "$1-$2"; last IFNAME;};
/po(\d+)/i && do {$suffix = "po-$1"; last IFNAME;};
return undef; # default case: invalid/unhandled iface name
}
return $self->{NAME} . '(conf-if-' . $suffix . ')#';
}
#
# Run a CLI command (or config command), checking for errors.
#
# Parameters:
# $cmd - The CLI command to run in the given context.
# $confmode - Is this a configuration command? 1 for yes, 0 for no
# $iface - Name of interface to exec config command against.
#
sub doCLICmd($$;$$)
{
my ($self, $cmd, $confmode, $iface) = @_;
$confmode ||= 0;
$iface ||= "";
my $output = "";
my $error = "";
my @active_sets;
my $exp = $self->{SESS};
my $id = "$self->{NAME}::doCLICmd()";
$self->debug("$id: called with: '$cmd', '$confmode', '$iface'\n",1);
if (!$exp) {
#
# Create the Expect object, lazy initialization.
#
# We'd better set a long timeout on Apcon switch
# to keep the connection alive.
$self->{SESS} = $self->createExpectObject();
if (!$self->{SESS}) {
warn "WARNING: Unable to connect to $self->{NAME}\n";
return (1, "Unable to connect to switch $self->{NAME}.");
}
$exp = $self->{SESS};
}
# Common patterns
my $catch_error_pat = [qr/% Error: (.+?)\n/,
sub {my $e = shift; $error = ($e->matchlist)[0];
exp_continue;}];
my $timeout_pat = [timeout => sub { $error = "timed out.";}];
my $get_output_pat = [$self->{CLI_PROMPT}, sub {my $e = shift;
$output = $e->before();}];
# Common pattern sets
my $get_output_set = [$get_output_pat];
#
# Sets of pattern sets for execution follow.
#
# Just pop off one command without going into config mode.
my @single_command_sets = ();
push (@single_command_sets,
[
[$self->{CLI_PROMPT}, sub {my $e = shift; $e->send("$cmd\n")}]
],
$get_output_pat
);
# Perform a single config operation (go into config mode).
my @single_config_sets = ();
push (@single_config_sets,
[
[$self->{CLI_PROMPT}, sub {my $e = shift;
$e->send("conf t\n$cmd\nend\n");}]
],
$get_output_pat
);
# Do an interface config operation (go into iface-specific config mode).
my @iface_config_sets = ();
push (@iface_config_sets,
[
[$self->{CLI_PROMPT}, sub {my $e = shift;
$e->send("conf t\ninterface $iface\n$cmd\nend\n");}]
],
$get_output_pat
);
# Pick "set of sets" to use with Expect based on how this method
# was called.
if ($confmode) {
if ($iface) {
@active_sets = @iface_config_sets;
} else {
@active_sets = @single_config_sets;
}
} else {
@active_sets = @single_command_sets;
}
$exp->clear_accum();
$exp->send("\cC"); # Get a command prompt into the Expect accumulator.
# Match across the selected set of patterns.
my $i = 1;
foreach my $patset (@active_sets) {
$self->debug("Match set: $i.\n",2);
$i++;
$exp->expect($CLI_TIMEOUT,
$catch_error_pat,
@$patset,
$timeout_pat);
if ($error || $exp->error()) {
$self->debug("error string: $error\n",2);
$self->debug("exp error: " . ($exp->error()) . "\n",2);
} else {
$self->debug("exp match: " . ($exp->match()) . "\n",2);
}
$self->debug("exp before: " . ($exp->before()) . "\n",2);
$self->debug("exp after: " . ($exp->after()) . "\n",2);
}
if (!$error && $exp->error()) {
$error = $exp->error();
}
if ($error) {
$self->debug("$id: Error in doCLICmd: $error\n",1);
return (1, $error);
} else {
return (0, $output);
}
}
#
# Prints out a debugging message, but only if debugging is on. If a level is
# given, the debuglevel must be >= that level for the message to print. If
# the level is omitted, 1 is assumed
#
# Usage: debug($self, $message, $level)
#
sub debug($$;$) {
my $self = shift;
my $string = shift;
my $debuglevel = shift;
if (!(defined $debuglevel)) {
$debuglevel = 1;
}
if ($self->{DEBUG} >= $debuglevel) {
print STDERR $string;
}
}
......@@ -39,6 +39,7 @@ use English;
use SNMP;
use snmpit_lib;
use Port;
use force10_expect;
#
# These are the commands that can be passed to the portControl function
......@@ -90,6 +91,10 @@ my $ChassisInfo = {
#
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:
# ifindex : positive integer corresponding to the interface index
......@@ -171,6 +176,7 @@ sub new($$$;$) {
#
$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->{POIFINDEX} = {}; # Port channel ifindex map.
if ($self->{DEBUG}) {
print "snmpit_force10 module initializing... debug level $self->{DEBUG}\n";
......@@ -206,8 +212,23 @@ sub new($$$;$) {
#
# Bomb out if the session could not be established
#
warn "ERROR: Unable to connect via SNMP to $self->{NAME}\n";
return undef;
warn "ERROR: Unable to connect via SNMP to $self->{NAME}\n";
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";
}
#
......@@ -215,7 +236,6 @@ sub new($$$;$) {
# 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
#
my $OS_details = snmpitGetFatal($self->{SESS},["sysDescr",0],30);
print "Switch $self->{NAME} is running $OS_details\n" if $self->{DEBUG};
......@@ -302,6 +322,13 @@ sub readifIndex($) {
$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
......@@ -1678,38 +1705,91 @@ sub disablePortTrunking($$) {
#
# 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
#
# 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.
# an ifindex if a channel is found and/or only one port is given.
#
sub getChannelIfIndex($@) {
my $self = shift;
my @ports = @_;
my @ifIndexes = $self->convertPortFormat($PORT_FORMAT_IFINDEX,@ports);
my @modports = $self->convertPortFormat($PORT_FORMAT_MODPORT,@ports);
my $ifindex = undef;
my $id = "$self->{NAME}::getChannelIfIndex()";
$self->debug("$id: entering ".join(",",@ports)."\n");
return undef
if (! @ifIndexes);
$self->debug("$id: ".join(",",@ifIndexes)."\n");
if (! @modports);
$self->debug("$id: ".join(",",@modports)."\n");
my ($rows) = snmpitBulkwalkWarn($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 ($oid,$channelid,$members) = @{$result};
$self->debug("$id: got $oid, $channelid, members $members\n",2);
# Apparently the iid (channelid) is not filled in when
# walking this OID tree. Check for a null iid, and substitute
# the last component of the returned OID if necessary.
if (!$channelid) {
$channelid = substr($oid, rindex($oid,'.') + 1);
}
# 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;
}
}
}
}
}
#
# 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; }
# If we don't yet have a portchannel index, and a single port was
# passed in, just return the ifindex for that port (single wire trunks).
if (!$ifindex && scalar(@ports) == 1) {
if (exists($self->{IFINDEX}{$ports[0]})) {
$ifindex = $self->{IFINDEX}{$ports[0]};
}
}
$self->debug("$id: $ifindex\n");
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
......@@ -1720,16 +1800,19 @@ sub getChannelIfIndex($@) {
# vlan_numbers: An array of 802.1Q VLAN numbers to operate on
# Returns 1 on success, 0 otherwise
#
# XXX: incomplete: does not operate on portchannels, only single-wire trunks.
#
sub setVlansOnTrunk($$$$) {
my ($self, $modport, $value, @vlan_numbers) = @_;
my ($RetVal);
my $errors = 0;
my $id = $self->{NAME} . "::setVlansOnTrunk";
$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)
#
......@@ -1742,18 +1825,29 @@ sub setVlansOnTrunk($$$$) {
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) {
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 {
$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;
}
#
# Used to flush FDB entries easily
#
......@@ -1761,7 +1855,6 @@ sub setVlansOnTrunk($$$$) {
#
sub resetVlanIfOnTrunk($$$) {
my ($self, $modport, $vlan) = @_;
my ($ifIndex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX,$modport);
my $id = $self->{NAME} . "::resetVlansOnTrunk";
$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