Commit 259c9475 authored by Leigh B Stoller's avatar Leigh B Stoller

Rework the vlag tag reservation code so that it works properly from

within an elabinelab.

snmpit -A -C can now take a list of vlanids to work on
(reserve,unreserve) and -A can take a block of tags to try, as for
coordinating tags with another protogeni CM.

Add a new elabinelab proxy version until my changes make it out.
parent bfa21396
......@@ -58,6 +58,7 @@ sub doSetOpenflowController($$$);
sub doSetOpenflowListener($$$);
sub doEnableOpenflowListener($$);
sub doReserveVlanTags($$@);
sub doUnReserveVlanTags($$@);
#
# Defaults
......@@ -177,12 +178,11 @@ my @SAVEARGV = @ARGV;
my %opt = ();
Getopt::Long::Configure("no_ignore_case");
GetOptions(\%opt,
'a','c','d','e','b','B=s@','g','h','i=s@','l+','m=s@','M','n', 'A',
'N=s@','o=s@','p=s','q','r','s', 'S=s@','t','E=s','T=s','u=s','U','v=s','w',
'a','c','d','e','b','B=s@','g','h','i=s@','l+','m=s@','M','n', 'A', 'C',
'N=s@','p=s','q','r','s', 'S=s@','t','E=s','T=s','u=s','U','v=s','w',
'y=s','x=s','z=s','F','L=s','O', 'D', 'R', 'f', 'X', 'Z', 'vlan_tag=i',
'of-disable=s', 'of-enable=s', 'of-controller=s', 'of-listener=s',
'redirect-err');
# Unused: f,j
'o=s@{1,1}', 'redirect-err');
if ($opt{h}) {
exit &usage;
......@@ -220,7 +220,8 @@ my $pid;
my $eid;
my $experiment;
my @ports;
my @optvlanids = ();
my @optvlanids = ();
my %optvlantags = ();
my $equaltrunking = 0;
my $this_user;
my $ofconnstr; # Openflow connection string, for controller
......@@ -255,18 +256,36 @@ if ($opt{m} || $opt{o}) {
($pid, $eid) = (shift @ARGV, shift @ARGV);
}
}
if ($opt{t} || $opt{r} || $opt{D} || $opt{R} || $opt{X} || $opt{A}) {
if ($opt{t} || $opt{r} || $opt{D} || $opt{R} || $opt{X} || $opt{A} || $opt{C}) {
#
# Options that take 'pid eid'
#
if (@ARGV < 2) {
tberror "pid/eid reqired!";
exit &usage;
} else {
($pid, $eid) = (shift @ARGV, shift @ARGV);
if (!defined($pid)) {
if (@ARGV < 2) {
tberror "pid/eid reqired!";
exit &usage;
} else {
($pid, $eid) = (shift @ARGV, shift @ARGV);
}
}
if (@ARGV) {
@optvlanids = @ARGV;
if ($opt{A} || ($opt{C} && !($opt{r} || $opt{o}))) {
# Allow a comma separated list of tags per lan.
foreach my $arg (@ARGV) {
if ($arg =~ /^[-\w]*,\d*/) {
my @tmp = split(',', $arg);
my $id = shift(@tmp);
push(@optvlanids, $id);
$optvlantags{$id} = \@tmp;
}
else {
push(@optvlanids, $arg);
}
}
}
else {
@optvlanids = @ARGV;
}
}
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}
|| $opt{U} || $opt{b}) {
......@@ -351,6 +370,9 @@ if ($opt{b}) { push @commands, ["portstatus"]; }
if ($opt{F}) { push @commands, ["synchleader"]; }
if ($opt{A}) { push @commands, ["reservetags"]; }
# This option combines with reset/remove
if ($opt{C} && !($opt{r} || $opt{o})) { push @commands, ["unreservetags"]; }
#
# Commands that can appear once, and take an argument
#
......@@ -676,7 +698,7 @@ COMMAND: foreach my $command (@commands) {
}
last;
};
(/^tables$/ || /^reservetags$/) && do {
(/^tables$/ || /^reservetags$/ || /^unreservetags$/) && do {
# Grab all stacks that any ports in the experiment are members
# of.
# (We need the entire stack, since the VLAN may have to traverse
......@@ -720,6 +742,14 @@ COMMAND: foreach my $command (@commands) {
if (!$quiet);
next COMMAND;
}
if ($ELABINELAB) {
#
# Do not worry about the local state. Always go to outer
# boss to make sure that the vlans are removed.
#
@devicenames = getTestSwitches();
last;
}
@vlans = filterPlannedVlans(@vlans);
if (!@vlans) {
print "snmpit: $pid/$eid has VLANs, but none have been " .
......@@ -985,6 +1015,10 @@ COMMAND: foreach my $command (@commands) {
$exitval += doReserveVlanTags($experiment,\@stacks,@vlans);
last;
}; # /reservetags/ && do
/^unreservetags$/ && do {
$exitval += doUnReserveVlanTags($experiment,\@stacks,@vlans);
last;
}; # /reservetags/ && do
/synctables/ && do {
$exitval += syncVlansFromTables($experiment,\@stacks);
last;
......@@ -1787,7 +1821,8 @@ sub doReset($@) {
# Hand over to outer boss.
#
if ($ELABINELAB) {
return RemoteDoReset($experiment, scalar(@optvlanids) == 0, @vlans);
return RemoteDoReset($experiment,
scalar(@optvlanids) == 0, $opt{C}, @vlans);
}
my $errors = 0;
......@@ -1807,6 +1842,8 @@ sub doReset($@) {
}
foreach my $vlan (@existant_vlans) {
setVlanTag($vlan, 0);
clearReservedVlanTag($vlan)
if ($opt{C});
VLan->RecordVLanDeletion($vlan);
}
}
......@@ -2132,8 +2169,10 @@ sub doReserveVlanTags($$@) {
my $errors = 0;
my @stacknames = map { $_->{STACKID} } @$stacks;
print STDERR "@stacknames\n";
if ($ELABINELAB) {
return RemoteDoReserveVlanTags($experiment, \%optvlantags, @vlanids);
}
#
# First do sanity checks on the entire set of vlans
#
......@@ -2150,6 +2189,10 @@ sub doReserveVlanTags($$@) {
$errors++;
next;
}
elsif (@planned == 0) {
# Default to the provided stack. Correct thing to do?
@planned = @stacknames;
}
my $stack = $planned[0];
if (! (grep {$_ eq $stack} @stacknames)) {
......@@ -2168,29 +2211,121 @@ sub doReserveVlanTags($$@) {
my @assigned = ();
foreach my $vlan (values(%vlans)) {
my $vlanid = $vlan->id();
my $stack = $vstacks{"$vlanid"};
my $tag = getReservedVlanTag($vlanid);
my $vlanid = $vlan->id();
my $stack = $vstacks{"$vlanid"};
my $isblock = 0;
my @tags = ();
# Do nothing if tag already assigned.
next
if ($tag);
#
# Option to assign a specific tag. This needs to be better.
#
if (exists($optvlantags{$vlanid})) {
@tags = @{ $optvlantags{$vlanid} };
# In block mode (multiple tags) do not signal an error on failure.
# Caller will figure it out.
$isblock = (scalar(@tags) > 1 ? 1 : 0);
}
elsif ($vlan->GetReservedVlanTag()) {
# Wanted any tag, but lan has a tag.
next;
}
again:
if (@tags) {
$next_vlan_tag = pop(@tags);
}
#
# If we can assign a tag, remember we did so that we can
# undo whatever we did, if there is an error.
#
$tag = $stack->newVlanNumber($vlanid);
if ($tag <= 0) {
my $tag = $stack->newVlanNumber($vlanid);
if ($tag) {
push(@assigned, [$vlan,$tag]);
}
elsif (!$isblock) {
print STDERR "Could not pre-reserve tag for $vlan\n";
$errors++;
last;
}
push(@assigned, $vlan);
goto again
if (@tags);
}
if ($errors) {
foreach my $vlan (@assigned) {
clearReservedVlanTag($vlan->id());
foreach my $ref (@assigned) {
my ($vlan, $tag) = @{ $ref };
$vlan->ClearReservedVlanTag($tag);
}
}
return $errors;
}
sub doUnReserveVlanTags($$@) {
my $experiment = shift;
my $stacks = shift;
my @vlanids = @_;
my %vlans = ();
my %vstacks = ();
my $errors = 0;
my @stacknames = map { $_->{STACKID} } @$stacks;
if ($ELABINELAB) {
return RemoteDoUnReserveVlanTags($experiment, \%optvlantags, @vlanids);
}
#
# First do sanity checks on the entire set of vlans
#
foreach my $id (@vlanids) {
my $vlan = VLan->Lookup($id);
if (!defined($vlan)) {
die("Could not locate vlan $id in the DB\n");
}
$vlans{"$id"} = $vlan;
my @planned = getPlannedStacksForVlans($id);
if (@planned > 1) {
print STDERR "$vlan crosses multiple stacks. Cannot reserve tag\n";
$errors++;
next;
}
elsif (@planned == 0) {
# Default to the provided stack. Correct thing to do?
@planned = @stacknames;
}
my $stack = $planned[0];
if (! (grep {$_ eq $stack} @stacknames)) {
print STDERR "$vlan is in stack $stack, but not in given stacks!\n";
$errors++;
next;
}
#
# Not allowed to clear the reservation if the lan exists.
#
if ($stack_ids{$stack}->vlanExists($id)) {
print STDERR
"$vlan exists on stack $stack; cannot clear reserved tag\n";
$errors++;
next;
}
}
return $errors
if ($errors);
#
# Now clear the reservations.
#
foreach my $vlan (values(%vlans)) {
my $vlanid = $vlan->id();
if (exists($optvlantags{$vlanid})) {
foreach my $tag (@{ $optvlantags{$vlanid} }) {
$vlan->ClearReservedVlanTag($tag);
}
}
else {
$vlan->ClearReservedVlanTag();
}
}
return $errors;
......@@ -2378,7 +2513,11 @@ sub doDeleteVlan($@) {
#
if (defined($experiment)) {
foreach my $vlan_name (@vlan_names) {
my $vlan = VLan->Lookup($experiment, $vlan_name);
my $vlan = VLan->Lookup($vlan_name);
if (defined($vlan) &&
!$experiment->SameExperiment($vlan->GetExperiment())) {
die("$vlan is not in the correct experiment\n");
}
if (!defined($vlan)) {
die("VLan object for $vlan_name does not exist\n");
}
......@@ -2400,7 +2539,7 @@ sub doDeleteVlan($@) {
if ($ELABINELAB) {
if (defined($experiment)) {
foreach my $vlan (values(%vlans)) {
if (RemoteDeleteVlan($vlan) == 0) {
if (RemoteDeleteVlan($vlan, $opt{C}) == 0) {
if ($vlan->Destroy() != 0) {
print STDERR "*** Could not destroy $vlan\n";
}
......@@ -2415,7 +2554,7 @@ sub doDeleteVlan($@) {
foreach my $vid (values(%vlan_ids)) {
push(@vidlist, $vid);
}
$errors = RemoteDoReset(undef, 0, @vidlist);
$errors = RemoteDoReset(undef, 0, $opt{C}, @vidlist);
}
return $errors;
}
......@@ -2462,15 +2601,23 @@ sub doDeleteVlan($@) {
#
next
if ($errors && exists($notdeleted{$vlan_name}));
# Always delete from the vlans table.
VLan->RecordVLanDeletion($vlan_id) == 0
or $errors++;
next
if (!exists($vlans{$vlan_name}));
if (!exists($vlans{$vlan_name})) {
if ($opt{C}) {
VLan::ClearReservedVlanTag($vlan_name) == 0
or $errors++;
}
next;
}
my $vlan = $vlans{$vlan_name};
$vlan->ClearReservedVlanTag()
if ($opt{C});
if ($vlan->IsManual()) {
$vlan->Destroy() == 0
or $errors++;
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
use RPC::XML::Parser;
use Data::Dumper;
#
# Snmpit proxy for ElabInElab.
#
sub usage()
{
print STDOUT "Usage: snmpit.proxy ".
"[-d] [-o outfile] -p <pid> -e <eid> <xmldoc>\n";
exit(-1);
}
my $optlist = "dnp:e:o:";
my $debug = 1;
my $dlevel = 1;
my $impotent = 0;
my $exitval = 0;
my $output;
my $outfile;
my $pid;
my $eid;
my @inner_ids = ();
my @outer_ids = ();
my %outer_vlans = ();
my %mapping = ();
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
# Load the Testbed support stuff.
use lib "@prefix@/lib";
use libdb;
use libaudit;
use libtestbed;
use User;
use Experiment;
use Lan;
use Interface;
use Node;
# Locals
my $exptidx;
# Protos
sub SetupVlans($);
sub MakeVlan($);
sub DestroyVlans($);
sub PortControl($);
sub Trunk($);
sub List($);
sub MapVlans(@);
sub ReserveVlanTags($);
sub UnReserveVlanTags($);
sub fatal($);
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
if (defined($options{"p"})) {
$pid = $options{"p"};
#
# Untaint the arguments.
#
if ($pid =~ /^([-\w\.]+)$/) {
$pid = $1;
}
else {
die("Tainted argument $pid!\n");
}
}
if (defined($options{"e"})) {
$eid = $options{"e"};
if ($eid =~ /^([-\w\.]+)$/) {
$eid = $1;
}
else {
die("Tainted argument $eid!\n");
}
}
if (defined($options{"o"})) {
$outfile = $options{"o"};
if (! ($outfile =~ /^\/var\/tmp\//)) {
fatal("$outfile does not resolve to an appropriate directory!");
}
}
usage()
if (@ARGV != 1 || !defined($pid) || !defined($eid));
#
# Need a version of system that ensures that all output goes to STDERR
# since anything that goes to STDOUT will confuse the caller (xmlrpc).
# This is sad; the results should not go back as STDOUT text.
#
sub mysystem($)
{
my ($command) = @_;
open(PIPE,"$command 2>&1 |") or return -1;
while (<PIPE>) {
print STDERR $_;
}
close(PIPE);
return $?;
}
#
# Log audit since it is hard to debug this one.
#
LogStart(0, undef, LIBAUDIT_LOGTBOPS()|LIBAUDIT_NODELETE());
my $xmldoc = $ARGV[0];
# Note different taint check (allow /).
if ($xmldoc =~ /^([-\w\.\/]+)$/) {
$xmldoc = $1;
}
else {
fatal("Tainted argument $xmldoc");
}
# Cause we are invoked from the xmlrpc server with this name format.
if (! ($xmldoc =~ /^\/var\/tmp\/php[-\w]+/)) {
fatal("$xmldoc does not resolve to an appropriate directory!");
}
if (! -e $xmldoc) {
fatal("$xmldoc does not exist!");
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# Sanity check. Must be an ElabInElab experiment and user must have
# permission (be the creator).
#
my $experiment = Experiment->Lookup($pid, $eid);
if (!defined($experiment)) {
fatal("Experiment $pid/$eid is not active!");
}
$exptidx = $experiment->idx();
if (!$experiment->AccessCheck($this_user, TB_EXPT_MODIFY)) {
fatal("You do not have permission to swap or modify this experiment!");
}
#
# Open the file and pass the stream to the parser.
#
open(XMLDOC, $xmldoc) or
fatal("$xmldoc could not be opened for reading!");
my $parser = RPC::XML::Parser->new();
my $goo = $parser->parse(*XMLDOC);
if (! ref($goo)) {
fatal("$xmldoc could not be parsed!");
}
my $opargs = $goo->value();
if (! (exists($opargs->{'op'}) && exists($opargs->{'args'}))) {
fatal("missing arguments in xmlgoo");
}
my $op = $opargs->{'op'};
my $args = $opargs->{'args'};
# Add stuff for log message if sent.
AddAuditInfo("message", $op . "\n\n" . Dumper($args));
#
# I'm going to serialize this for now. When the script exits, the lock
# will be released.
#
DBQueryFatal("select get_lock('snmpit.proxy', 999999)");
SWITCH: for ($op) {
/setup/ && do {
($exitval, $output) = SetupVlans($args);
last;
};
/makevlan/ && do {
($exitval, $output) = MakeVlan($args);
last;
};
/destroy/ && do {
($exitval, $output) = DestroyVlans($args);
last;
};
/trunk/ && do {
($exitval, $output) = Trunk($args);
last;
};
/list/ && do {
($exitval, $output) = List($args);
last;
};
/portcontrol/ && do {
($exitval, $output) = PortControl($args);
last;
};
/^reserve$/ && do {
($exitval, $output) = ReserveVlanTags($args);
last;
};
/^unreserve$/ && do {
($exitval, $output) = UnReserveVlanTags($args);
last;
};
# default case
fatal("Unknown operation $op");
}
# Update with output for log message if sent.
AddAuditInfo("message",
"$op:\n\n" . Dumper($args) . "\n\n" .
(defined($output) ? "$output" : "") . "\n");
#
# Terminate the log capture so that we can print the response to STDOUT
# for the RPC server.
#
if ($exitval || $debug) {
# Force error exit so email is sent.
LogEnd($exitval + $debug);
}
else {
LogAbort();
}
if (defined($output)) {
if (defined($outfile)) {
open(OUTF, ">>$outfile") or
fatal("$outfile could not be opened for writing!");
print OUTF $output;
close(OUTF);
}
else {
print $output;
}
}
exit($exitval);
#
# Setup vlans for an inner emulab. For each vlan id, gather up all if
# its member ports (node:iface), along with the speed and duplex for
# each port, which goes into the interfaces table so that the real
# snmpit can find the right values.
#
# Note that this function assumes the experiment stack. See MakeVlan()
# below for the more basic function to create a single vlan on a stack.
#
sub SetupVlans($)
{
my ($argtable) = @_;
my $vlantable = {};
my %vmaptable = ();
my $attrtable = {};
my %nodes = ();
my @outer_ids = ();
my $errors = 0;
foreach my $id (keys(%{ $argtable })) {
my $vtag = $argtable->{$id}->{'virtual'};
my $class = $argtable->{$id}->{'stack'};
my $members = $argtable->{$id}->{'members'};
my $trunk_mode = $argtable->{$id}->{'trunk_mode'}
if (exists($argtable->{$id}->{'trunk_mode'}));
if (! ($id =~ /^[\d]+$/)) {
fatal("SetupVlans: Illegal id '$id'");
}
if (! ($vtag =~ /^[\-\w]+$/)) {
fatal("SetupVlans: Illegal vname '$vtag'");
}
if (!defined($class)) {
$class = "Experimental";
}
elsif ($class ne "Control" && $class ne "Experimental") {
fatal("SetupVlans: Illegal class '$class'");
}
foreach my $port (keys(%{ $members })) {
my $speed = $members->{$port}->{'speed'};
my $duplex = $members->{$port}->{'duplex'};
my $trunk = $members->{$port}->{'trunk'};
my $node;
my $iface;
if (! ($port =~ /^[\-\w]+\:[\-\w]+$/)) {
fatal("SetupVlans: Illegal port '$port'");
}
if (! ($speed =~ /^[\d]+$/)) {
fatal("SetupVlans: Illegal speed '$speed'");
}
if (! ($duplex eq "full" || $duplex eq "half")) {
fatal("SetupVlans: Illegal duplex '$duplex'");
}
if (! exists($vlantable->{$id})) {
$vlantable->{$id} = {};
$attrtable->{$id} = {"virtual" => $vtag,
"stack" => $class,
"trunk_mode" => $trunk_mode};
}
$vlantable->{$id}->{$port} = [$speed, $duplex, $trunk];
# For doing access and sanity checks below.
if ($port =~ /^(.+):(.+)$/) {
($node,$iface) = ($1, $2);
}
$nodes{$node} = []
if (!exists($nodes{$node}));
push(@{ $nodes{$node} }, $iface);
}
}
#
# First check permission on the nodes. snmpit is going to repeat this
# operation, but we have to do it here cause we first mess with the
# speed and duplex values in the interfaces table for each node, cause
# snmpit uses those values when setting up the vlan.
#
if (!TBNodeAccessCheck($UID, TB_NODEACCESS_MODIFYVLANS, keys(%nodes))) {
die("*** $0:\n",
" You do not have permission to modify some of the nodes\n" .
" that will be affected by the operation you requested\n");
}
# Stoller wrote:
# Sanity check the inner id numbers. If they already exist in the
# mapping table, then bail now. We could probably support this, but
# I do not see a reason to yet.
# Sklower explains:
# for the federation boss, in order to calculate which
# vlans go on which inter site trunks it's convenient to
# push the vlan handling for master site's parent into
# a pseudo-switch-module, but that will cause the parent
# to be asked to add groups of interfaces in chunks.
# my $query_result =
# DBQueryFatal("select * from elabinelab_vlans ".
# "where pid='$pid' and eid='$eid' and ".
# # Join "id='foo'" with ORs
# "(" .
# join(' OR ', map("inner_id='$_'", keys(%$vlantable))) .
# ")");
# if ($query_result->numrows) {
# my @ids = keys(%$vlantable);
#
# die("*** $0:\n",
# " One of the inner vlan ids (@ids) already exists!\n");
# }
#
# Okay, sanity check the interfaces for each node. They have to be real
# interfaces, marked as TBDB_IFACEROLE_EXPERIMENT(), nothing else. Also,
# the IP must not be set; if the IP is set, its being used for the inner
# control network, and we do not let those interfaces change.
#
foreach my $node (keys(%nodes)) {