Commit 82fc120d authored by Robert Ricci's avatar Robert Ricci

Semi-major reworking of the main body of snmpit, in order to support

multiple operations. For example, you can now remove multiple VLANs with
one command, like:
snmpit -o foo -o bar

You can now give more than one -i option, so that you can give a list of
more than one switch to operate on, like:
snmpit -i cisco3 -i cisco4 -l

Converted from using Getopt::Std to Getopt::Long, which is more flexible and
better documented.

All the 'worker' ( do*() ) functions now take a list of stacks as the first
argument, rather than using a global @stacks variable.

Fixed up the usage message, which was out of date in some cases, and
innacurate in others.
parent fa9f1a49
......@@ -16,7 +16,7 @@ use libdb;
use snmpit_lib;
use English;
use Getopt::Std;
use Getopt::Long;
use strict;
#
......@@ -39,10 +39,13 @@ Usage: $0 [-h] [-v] [-i device]
[-o name]
[-r pid eid]
[-t pid eid]
[-d ports] [-e ports] [-a ports]
[-p <10|100> ports] [-u <half|full> ports]
General:
-h Display this help message
-v Verbose mode
-i <device> Operate on <device>, overriding default device list
-i <device> Operate on <device>, overriding default device list. Can be
given multiple times
VLAN Control:
-t <pid> <eid> Create all VLANs from database tables for an experiment
......@@ -53,24 +56,26 @@ VLAN Control:
-o <name> Delete the VLAN with name <name>
Port Control:
-s List all ports, and show configuration information
-g Get port statistics
-d <ports> Disable <ports>
-e <ports> Enable <ports>
-a <ports> Enable auto-negotiation of port speed/duplex
-p <ports> Set speed of <ports> to 10 or 100 Mbps
-u <ports> Set duplex of <ports> to half or full
-s List all ports, and show configuration information
-g Get port statistics
-d <ports> Disable <ports>
-e <ports> Enable <ports>
-a <ports> Enable auto-negotiation of port speed/duplex
-p <10|100> <ports> Set speed of <ports> to 10 or 100 Mbps
-u <half|full> <ports> Set duplex of <ports> to half or full
More than one operation can be specified - However, beware that the order in
which operations will occur is undefined, and some combinations of operations
(ie. -d and -e) are non-sensical.
END
return 1;
}
my $opts = 'hlvstri:m:o:p:u:deag';
my %opt = ();
getopts($opts,\%opt);
GetOptions(\%opt,'h','l','v','s','t','r','i=s@','m=s@','o=s@','p=s','u=s','d',
'e','a','g');
if ($opt{h}) {
exit &usage;
......@@ -81,16 +86,6 @@ if ($opt{v}) {
print "Debug level is $debug\n";
}
#
# Make sure the user specified exactly one operation
#
my @commandOpts = ('l','m','o','s','t','r','d','e','a','p','u','g');
if (!exactlyOne(map {$opt{$_}} @commandOpts)) {
warn "ERROR: You must specify exactly one of " .
join(",", map {"-$_"} @commandOpts) . "\n";
exit &usage;
}
#
# Values that may have been passed on the command line
#
......@@ -106,11 +101,11 @@ if ($opt{t} || $opt{r}) {
#
# Options that take 'pid eid'
#
if (@ARGV != 2) {
if (@ARGV < 2) {
warn "ERROR: pid/eid reqired!\n";
exit &usage;
} else {
($pid, $eid) = @ARGV;
($pid, $eid) = (shift @ARGV, shift @ARGV);
}
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}) {
#
......@@ -132,63 +127,71 @@ if ($opt{t} || $opt{r}) {
# so that we can use switch-like constructs later. While we're at it, we
# pull out any arguments that were given in the $opt{} values.
#
my $operation;
my $vlan_name;
my $portcommand;
if ($opt{l}) {
$operation = "listvlans";
} elsif ($opt{s}) {
$operation = "listports";
} elsif ($opt{g}) {
$operation = "getstats";
} elsif ($opt{t}) {
$operation = "tables";
} elsif ($opt{r}) {
$operation = "reset";
} elsif ($opt{m}) {
$operation = "make";
$vlan_name = $opt{m};
} elsif ($opt{o}) {
$operation = "remove";
$vlan_name = $opt{o};
} elsif ($opt{d}) {
$operation = "portcontrol";
$portcommand = "disable";
} elsif ($opt{e}) {
$operation = "portcontrol";
$portcommand = "enable";
} elsif ($opt{a}) {
$operation = "portcontrol";
$portcommand = "auto";
} elsif ($opt{p}) {
$operation = "portcontrol";
my @commands;
#
# Simple commands
#
if ($opt{l}) { push @commands, ["listvlans"]; }
if ($opt{s}) { push @commands, ["listports"]; }
if ($opt{g}) { push @commands, ["getstats"]; }
if ($opt{t}) { push @commands, ["tables"]; }
if ($opt{r}) { push @commands, ["reset"]; }
#
# Commands that can appear once, and take an agurment
#
if ($opt{d}) { push @commands, ["portcontrol","disable"]; }
if ($opt{e}) { push @commands, ["portcontrol","enable"]; }
if ($opt{a}) { push @commands, ["portcontrol","auto"]; }
#
# Commands that can occur more than once
#
if ($opt{m}) {
foreach my $name (@{$opt{m}}) {
push @commands, ["make",$name];
}
}
if ($opt{o}) {
foreach my $name (@{$opt{o}}) {
push @commands, ["remove",$name];
}
}
#
# Commands that require 'translation' of their arguments
#
if ($opt{p}) {
#
# We'll put the argument in the form needed by the portControl function
#
if ($opt{p} =~ /^100/) {
$portcommand = "100mbit";
push @commands, ["portcontrol","100mbit"];
} elsif ($opt{p} =~ /^10/) {
$portcommand = "10mbit";
push @commands, ["portcontrol","10mbit"];
} else {
die "Bad port speed: $opt{p}. Valid values are 10 and 100\n";
}
} elsif ($opt{u}) {
}
if ($opt{u}) {
#
# We'll put the argument in the form needed by the portControl function
#
$operation = "portcontrol";
if ($opt{u} =~ /half/) {
$portcommand = "half";
push @commands, ["portcontrol","half"];
} elsif ($opt{u} =~ /full/) {
$portcommand = "full";
push @commands, ["portcontrol","full"];
} else {
die "Bad port duplex: $opt{u}. Valid values are full and half\n";
}
} else {
}
if (!@commands) {
die "No operation given\n";
}
debug("Operation is $operation\n");
######################################################################
# Step 3 - Set up the stack objects
......@@ -197,53 +200,6 @@ debug("Operation is $operation\n");
# stack objects
######################################################################
if ($TESTMODE) {
print "Test mode, exiting without touching hardware\n";
exit(0);
}
#
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
#
snmpit_lib::init($debug);
#
# Discover the set of devices we need to talk to. This differs depending
# on the operation which we're performing. We also get a list of all ports
# and vlan IDs involved in this operation, if appropriate
#
my @devicenames;
my @vlans;
SWITCH: for ($operation) {
(/listvlans/ || /getstats/) && do {
@devicenames = $opt{i}? $opt{i} : getTestSwitches();
};
(/listports/ || /make/ || /remove/) && do {
@devicenames = $opt{i}? $opt{i} :
(@ports? getDeviceNames(@ports) : getTestSwitches());
last;
};
(/tables/) && do {
@vlans = getExperimentVlans($pid,$eid);
@ports = getVlanPorts(@vlans);
@devicenames = $opt{i}? $opt{i} : getDeviceNames(@ports);
};
(/reset/) && do {
#
# When we reset, we operate on all test switches, just to be safe
#
@vlans = getExperimentVlans($pid,$eid);
@devicenames = $opt{i}? $opt{i} : getTestSwitches();
};
(/portcontrol/) && do {
@devicenames = $opt{i}? $opt{i} : getDeviceNames(@ports);
};
}
debug("Device names: " . join(",",@devicenames) . "\n");
debug("Ports: " . join(",",@ports) . "\n");
#
# If this is an operation on an experiment, make sure that they have permission
# to modify that experiment
......@@ -264,6 +220,7 @@ if ($pid && $eid) {
# If their operation involves a set of ports, make sure that the caller has
# access to the nodes that the ports are on
#
if (@ports) {
my @nodes = map /^([^:]+)/, @ports;
if (!TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYVLANS,@nodes)) {
......@@ -272,94 +229,157 @@ if (@ports) {
}
}
#
# Find out which stack each device belongs to
#
my %stacks = ();
foreach my $devicename (@devicenames) {
my $stack = getSwitchStack($devicename);
if (defined($stack)) {
push @{$stacks{$stack}}, $devicename;
}
if ($TESTMODE) {
print "Test mode, exiting without touching hardware\n";
exit(0);
}
#
# Now, make the object for each stack that we discovered
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
#
my @stacks;
foreach my $stack_id (keys %stacks) {
my $stack_type = getStackType($stack_id);
my $stack;
debug("Stack $stack_id has type $stack_type\n");
SWITCH: for ($stack_type) {
/cisco/ && do {
require snmpit_cisco_stack;
$stack = new snmpit_cisco_stack($stack_id,$debug,
@{$stacks{$stack_id}});
snmpit_lib::init($debug);
my $exitval;
foreach my $command (@commands) {
#
# Pull the operation and the arugments to it.
#
my ($operation,@args) = @$command;
debug("Operation is $operation\n");
#
# Discover the set of devices we need to talk to. This differs depending
# on the operation which we're performing. We also get a list of all ports
# and vlan IDs involved in this operation, if appropriate
#
my @devicenames;
my @vlans;
SWITCH: for ($operation) {
(/listvlans/ || /getstats/) && do {
@devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
last;
}; # /cisco/
/intel/ && do {
require snmpit_intel_stack;
$stack = new snmpit_intel_stack($stack_id,$debug,
@{$stacks{$stack_id}});
};
(/listports/ || /make/ || /remove/) && do {
@devicenames = $opt{i}? @{$opt{i}} :
(@ports? getDeviceNames(@ports) : getTestSwitches());
last;
};
(/tables/) && do {
@vlans = getExperimentVlans($pid,$eid);
@ports = getVlanPorts(@vlans);
@devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
last;
};
(/reset/) && do {
#
# When we reset, we operate on all test switches, just to be safe
#
@vlans = getExperimentVlans($pid,$eid);
@devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
last;
};
(/portcontrol/) && do {
@devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
last;
};
}
# 'default' case
die "Unknown stack type $stack_type for stack $stack_id\n";
debug("Device names: " . join(",",@devicenames) . "\n");
debug("Ports: " . join(",",@ports) . "\n");
#
# Find out which stack each device belongs to
#
my %stacks = ();
foreach my $devicename (@devicenames) {
my $stack = getSwitchStack($devicename);
if (defined($stack)) {
push @{$stacks{$stack}}, $devicename;
}
}
#
# Check for error in object creation and bail
# Now, make the object for each stack that we discovered
#
if (!$stack) {
die "Unable to connect to one or more switches, exiting\n";
} else {
push @stacks, $stack;
my @stacks;
foreach my $stack_id (keys %stacks) {
my $stack_type = getStackType($stack_id);
my $stack;
debug("Stack $stack_id has type $stack_type\n");
SWITCH: for ($stack_type) {
/cisco/ && do {
require snmpit_cisco_stack;
$stack = new snmpit_cisco_stack($stack_id,$debug,
@{$stacks{$stack_id}});
last;
}; # /cisco/
/intel/ && do {
require snmpit_intel_stack;
$stack = new snmpit_intel_stack($stack_id,$debug,
@{$stacks{$stack_id}});
last;
};
# 'default' case
die "Unknown stack type $stack_type for stack $stack_id\n";
}
#
# Check for error in object creation and bail
#
if (!$stack) {
die "Unable to connect to one or more switches, exiting\n";
} else {
push @stacks, $stack;
}
}
}
######################################################################
# Step 3 - Actually perfrom the operation
# Step 4 - Actually perfrom the operation
#
# Finally, we just call the helper function for the operation that
# is to be performed. Note that all functions use the global @stacks
# variable, so it isn't passed to every one.
# is to be performed.
######################################################################
my $exitval;
SWITCH: for ($operation) {
/listvlans/ && do {
$exitval = doListVlans();
last;
}; # /listvlans/ && do
/listports/ && do {
$exitval = doListPorts();
last;
}; # /listports/ && do
/getstats/ && do {
$exitval = doGetStats();
last;
}; # /ports/ && do
/tables/ && do {
$exitval = doVlansFromTables(@vlans);
last;
}; # /tables/ && do
/reset/ && do {
$exitval = doReset(@vlans);
last;
};
/make/ && do {
$exitval = doMakeVlan($vlan_name,@ports);
last;
};
/remove/ && do {
$exitval = doDeleteVlan($vlan_name);
last;
};
/portcontrol/ && do {
$exitval = doPortControl($portcommand,@ports);
};
SWITCH: for ($operation) {
/listvlans/ && do {
$exitval += doListVlans(\@stacks);
last;
}; # /listvlans/ && do
/listports/ && do {
$exitval += doListPorts(\@stacks);
last;
}; # /listports/ && do
/getstats/ && do {
$exitval += doGetStats(\@stacks);
last;
}; # /ports/ && do
/tables/ && do {
$exitval += doVlansFromTables(\@stacks,@vlans);
last;
}; # /tables/ && do
/reset/ && do {
$exitval += doReset(\@stacks,@vlans);
last;
};
/make/ && do {
my ($vlan_name) = @args;
$exitval += doMakeVlan(\@stacks,$vlan_name,@ports);
last;
};
/remove/ && do {
my ($vlan_name) = @args;
$exitval += doDeleteVlan(\@stacks,$vlan_name);
last;
};
/portcontrol/ && do {
my $portcommand = @args;
$exitval += doPortControl(\@stacks,$portcommand,@ports);
};
}
}
exit $exitval;
......@@ -368,23 +388,6 @@ exit $exitval;
# Subs
######################################################################
#
# Return true if exactly one element of the given array is true
#
sub exactlyOne (@) {
my $one = 0;
foreach my $value ( @_ ) {
if ($value) {
if ($one) {
return 0; # More than one
} else {
$one = 1; # This is the first
}
}
}
return $one;
}
#
# Print given message to STDERR, only if debug mode is on
#
......@@ -397,7 +400,9 @@ sub debug($) {
#
# Lists all vlans on all stacks
#
sub doListVlans () {
sub doListVlans ($) {
my $stacks = shift;
my %vlans;
......@@ -405,7 +410,7 @@ sub doListVlans () {
# We need to 'coallate' the results from each stack by putting together
# the results from each stack, based on the VLAN identifier
#
foreach my $stack (@stacks) {
foreach my $stack (@$stacks) {
# TODO: Add a way to print ddep
my @vlanList = $stack->listVlans();
foreach my $vlan (@vlanList) {
......@@ -476,13 +481,15 @@ $vlan_id,$pideid, $vname, $members
#
# Lists all ports on all stacks
#
sub doListPorts() {
sub doListPorts($) {
my $stacks = shift;
#
# Get a listing from all stacks
#
my @portList = ();
foreach my $stack (@stacks) {
foreach my $stack (@$stacks) {
push @portList, $stack->listPorts;
}
......@@ -524,13 +531,15 @@ $port, $enabled,$up,$speed,$duplex
#
# Get statistics for all ports on all stacks
#
sub doGetStats() {
sub doGetStats($) {
my $stacks = shift;
#
# Get a listing from all stacks
#
my @statList = ();
foreach my $stack (@stacks) {
foreach my $stack (@$stacks) {
push @statList, $stack->getStats();
}
......@@ -564,16 +573,17 @@ $port, $inoctets, $inunicast,$innunicast,$indiscards,$inerr, $inunk, $out
# Creates all VLANs given. Looks up identifiers in the database to determine
# the membership.
#
sub doVlansFromTables(@) {
sub doVlansFromTables($@) {
my $stacks = shift;
my @vlans = @_;
my $errors = 0;
if (@stacks > 1) {
if (@$stacks > 1) {
die "VLAN creation accross multiple stacks is not yet supported\n" .
"Stacks are " . join(",",@stacks) . "\n";
"Stacks are " . join(",",@$stacks) . "\n";
}
my ($stack) = @stacks;
my ($stack) = @$stacks;
foreach my $vlan (@vlans) {
my @ports = getVlanPorts($vlan);
......@@ -623,7 +633,8 @@ sub doVlansFromTables(@) {
# VLANs are removed, irrespective of what the database says membership should
# be
#
sub doReset(@) {
sub doReset($@) {
my $stacks = shift;
my @vlans = @_;
my $errors = 0;
......@@ -631,7 +642,7 @@ sub doReset(@) {
#
# Just remove the VLAN from evey satck on which it exists
#
foreach my $stack (@stacks) {
foreach my $stack (@$stacks) {
if ($stack->vlanExists($vlan)) {
if (!$stack->removeVlan($vlan)) {
$errors++;
......@@ -647,17 +658,18 @@ sub doReset(@) {
# VLAN that already exists, as this can be used to add ports to an existing
# VLAN. If ports are given, they are put into the VLAN.
#
sub doMakeVlan($@) {
sub doMakeVlan($$@) {
my $stacks = shift;
my $vlan_name = shift;
my @ports = @_;
my $errors = 0;
if (@stacks > 1) {
if (@$stacks > 1) {
die "VLAN creation accross multiple stacks is not yet supported\n" .
"Stacks are " . join(",",@stacks) . "\n";
"Stacks are " . join(",",@$stacks) . "\n";
}
my ($stack) = @stacks;
my ($stack) = @$stacks;
#
# Create it if it doesn't already exist
......@@ -692,13 +704,14 @@ sub doMakeVlan($@) {
#
# Delete the given VLAN, if it exists
#
sub doDeleteVlan($) {
sub doDeleteVlan($$) {
my $stacks = shift;
my $vlan_name = shift;
my $errors = 0;
my $exists = 0;
foreach my $stack (@stacks) {
foreach my $stack (@$stacks) {
if ($stack->vlanExists($vlan_name)) {
$exists = 1;
print "Deleting VLAN $vlan_name ...\n";
......@@ -723,15 +736,16 @@ sub doDeleteVlan($) {
# Send $command to @ports.
# TODO: List of commands
#
sub doPortControl($@) {
sub doPortControl($$@) {
my $stacks = shift;
my $command = shift;
my @ports = @_;
if (@stacks > 1) {
if (@$stacks > 1) {
die "Port control accross multiple stacks is not yet supported\n" .
"Stacks are " . join(",",@stacks) . "\n";
"Stacks are " . join(",",@$stacks) . "\n";