Commit c648c2d4 authored by Kirk Webb's avatar Kirk Webb

Checkpoint. Almost there...

parent ddefb2d6
......@@ -36,7 +36,6 @@ use vars qw(@ISA @EXPORT);
use English;
use Tree::Binary;
use Net::IP;
use Socket;
# Global vars
my $debug = 0;
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2013 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# 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 Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
# Emulab wrapper class for the IP range buddy allocator. Handles all of the
# Emulab-specific goo around allocating address ranges.
#
package IPBuddyWrapper;
use strict;
use Exporter;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw ( );
use English;
use emdb;
use libtblog_simple;
use VirtExperiment;
use IPBuddyAlloc;
use Socket;
# Constants
# Prototypes
#
# Create a new IPBuddyAlloc wrapper object. Pass in a string specifying
# the type of address reservations to target.
#
sub new($$) {
my ($class, $type) = @_;
my $self = {};
return undef
unless defined($type);
# Get the address range corresponding to this type from the database.
# Currently we only support one range per type.
my $qres =
DBQueryWarn("select * from address_ranges where type='$type'");
return undef
if (!$qres);
if ($qres->numrows() != 1) {
tberror("More than one range entry found for this address ".
"type in the DB: $type\n");
return undef;
}
my ($baseaddr, $prefix, $type, $role) = $qres->fetchrow();
# IPBuddyAlloc throws exceptions.
my $buddy = eval { IPBuddyAlloc->New("$baseaddr/$prefix") };
if ($@) {
tberror("Could not allocate a new IP Buddy Allocator object: $@\n");
return undef;
}
$self->{'BUDDY'} = $buddy;
$self->{'ALLOC_RANGES'} = {};
$self->{'NEWLIST'} = [];
bless($self, $class);
return $self;
}
# Internal Accessors
sub _getbuddy($) { return $_[0]->{'BUDDY'}; }
sub _gettype($) { return $_[0]->{'TYPE'}; }
sub _allranges($) { return $_[0]->{'ALLOC_RANGES'}; }
sub _allnew($) { return $_[0]->{'NEWLIST'}; }
sub _getrange($$) { return $_[0]->_allranges()->{$_[1]}; }
sub _putrange($$$) { $_[0]->_allranges()->{$_[1]} = $_[2]; }
sub _newrange($$$) { $_[0]->_putrange($_[1],$_[2]);
push @{$_[0]->_allnew()}, $_[2]; }
#
# XXX: implement
#
sub lock($) {
my $self = shift;
return 1;
}
#
# XXX: implement
#
sub unlock($) {
my $self = shift;
return 1;
}
#
# Load ranges into this object from the Emulab database. Also, optionally
# add the subnets for a specified experiment to the set of reservations.
#
# $self - Reference to class instance.
# $vexperiment - (optional) VirtExperiment object reference. virtlans
# that are a member of this experiment will be added to the
# set of reserved address ranges.
#
sub loadReservedRanges($;$) {
my ($self, $virtexperiment) = @_;
my $bud = $self->_getbuddy();
my $ranges = $self->_getranges();
my $addrtype = $self->_gettype();
my $qres =
DBQueryWarn("select * from reserved_addresses where type='$addrtype'");
return -1
if (!$qres);
# Go through each row in the reserved addresses table for the type
# specified, and add the ranges to the internal buddy allocator.
# Create and stash an object for other bookkeeping.
while (my ($ridx, $pid, $eid, $exptidx, $rtime,
$baseaddr, $prefix, $type, $role) = $qres->fetchrow())
{
my $rval = eval { $bud->embedAddressRange("$baseaddr/$prefix") };
if ($@) {
tberror("Error while embedding reserved address range: $@\n");
return -1
}
$self->_putrange("$baseaddr/$prefix",
IPBuddyWrapper::Allocation->new($exptidx,
"$baseaddr/$prefix"));
}
# Add an experiment's virtlans if that parameter was passed in.
if (defined($virtexperiment)) {
if (ref($virtexperiment) ne "HASH" ||
!$virtexperiment->isa(VirtExperiment)) {
tberror("Argument was not a VirtExperiment object!\n");
return -1;
}
my $exptidx = $virtexperiment->exptidx();
my $virtlans = $virtexperiment->Table("virt_lans");
foreach my $virtlan (values %{$virtlans}) {
my $ip = inet_aton($virtlan->ip());
my $mask = inet_aton($virtlan->mask());
my $prefix = unpack('%32b*', $mask);
my $base = inet_ntoa($ip & $mask);
next if !$self->_getrange("$base/$prefix");
my $rval = eval { $bud->embedAddressRange("$base/$prefix") };
if ($@) {
tberror("Error while embedding experiment lan range: $@\n");
return -1;
}
$self->_putrange("$base/$prefix",
IPBuddyWrapper::Allocation->new($exptidx,
"$base/$prefix"));
}
}
return 0;
}
#
# Request an address range from the buddy IP address pool given the
# input (dotted quad) mask and a virt experiment to stash away for
# later when the code needs to push the reservations into the
# database.
#
sub requestAddressRange($$$) {
my ($self, $virtexperiment, $mask) = @_;
return undef unless
ref($virtexperiment) == "HASH" &&
defined($mask);
my $prefix;
if ($mask =~ /^\d+\.\d+\.\d+\.\d+$/) {
$prefix = unpack('%32b*', inet_aton($mask));
} elsif ($prefix =~ /^\d+$/) {
$prefix = $mask;
} else {
tberror("Invalid mask/prefix: $mask\n");
return undef;
}
my $exptidx = $virtexperiment->exptidx();
my $bud = $self->_getbuddy();
my $range = eval { $bud->requestAddressRange($prefix) };
if ($@) {
tberror("Error while requesting address range: $@");
return undef;
}
if (!defined($range)) {
tberror("Could not get a free address range!\n");
return undef;
}
$self->_newrange($range, IPBuddyWrapper::Allocation->new($exptidx,
$range));
return $range;
}
#
# Request the next address from the input range. It should have been
# previously allocated with requestAddressRange()
#
sub getNextAddress($$) {
my ($self, $range) = @_;
return undef
unless defined($range);
my $robj = $self->_getrange($range);
return undef
unless defined($robj);
return $robj->getNextAddress();
}
sub DESTROY($) {
my $self = shift;
$self->{'BUDDY'} = undef;
$self->{'ALLOC_RANGES'} = undef;
$self->{'NEWLIST'} = undef;
}
#
# XXX: implement
#
sub commitReservations($) {
my ($self) = @_;
return 0;
}
##############################################################################
#
# Internal module to keep track of address range allocations.
#
package IPBuddyWrapper::Allocation;
use strict;
use English;
use Net::IP;
use libtblog_simple;
sub new($$$) {
my ($class, $exptidx, $range) = @_;
my $self = {};
return undef unless
defined($exptidx) &&
defined($range);
my $ipobj = Net::IP->new($range);
if (!defined($ipobj)) {
tberror(Net::IP::Error() . "\n");
return undef;
}
$self->{'EXPTIDX'} = $exptidx;
$self->{'RANGE'} = $range;
$self->{'IPOBJ'} = $ipobj;
return $self;
}
# accessors
sub _getobj($) { return $_[0]->{'IPOBJ'}; }
sub _getrange($) { return $_[0]->{'RANGE'}; }
#
# Get next available address in the range. ('+' is overloaded in Net::IP).
#
sub getNextAddress($) {
my ($self) = @_;
my $curip = $self->_getobj();
if (++$curip) {
return $curip->ip();
}
return undef;
}
#
# Reset back to base address from this object's range.
#
sub resetAddress($) {
my $self = $shift;
my $ipobj = Net::IP->new($self->_getrange());
$self->{'IPOBJ'} = $ipobj;
}
sub DESTROY($) {
my $self = shift;
$self->{'EXPTIDX'} = undef;
$self->{'RANGE'} = undef;
$self->{'IPOBJ'} = undef;
}
# Required by perl
1;
......@@ -8,8 +8,8 @@ sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if(!DBTableExists("address_reservations")) {
DBQueryFatal("CREATE TABLE `address_reservations` ( ".
if(!DBTableExists("reserved_addresses")) {
DBQueryFatal("CREATE TABLE `reserved_addresses` ( ".
" `rsrvidx` int(10) unsigned NOT NULL default '0', ".
" `pid` varchar(48) NOT NULL default '', ".
" `eid` varchar(32) NOT NULL default '', ".
......
......@@ -5644,6 +5644,11 @@ sub InterpLinks($)
}
}
# Check nodes for shared members that require special address
# treatment. Perform address substitution and reservation
# on attached lans / lan members.
$self->CheckIPAddressReservations() == 0
or return -1;
# Write the vlans to the DB.
$self->UploadVlans() == 0
or return -1;
......@@ -7664,6 +7669,122 @@ sub SetUpTracing($$$$$)
return 0;
}
#
# Look for shared entities that require special IP address treatment.
# This usually means getting a new mutually exclusive address range,
# reserving it for this experiment in the database. Substitute
# addresses on co-located link/lan members to accomodate. This is
# _NOT_ a persisitent reservation. It is per experiment swap-in.
#
sub CheckIPAddressReservations($) {
my ($self) = @_;
# For stashing lans we need to take a closer look at.
my @sharedlans = ();
# Round 1, peel off lans that contain members that need special
# address treatment. These lans could probably be marked when
# such a node is added, but doing it here keeps this procedure
# self-contained.
foreach my $virtlan (values(%{ $self->vlans() })) {
next
if ($virtlan->_layer() != 2); # foo from the beyond.
foreach my $member ($virtlan->memberlist()) {
# Will this happen?
next
if (!exists($self->solution_portmap()->{$member}));
my $vnodename = $member->vnode();
my $pnodename = $self->solution_v2p()->{$vnodename};
my $pnode = $self->pnodes()->{$pnodename};
my $smode = $pnode->sharing_mode();
# Only looking for shared storage hosts at present,
# but this could easily be changed to add more.
if (defined($smode) && $smode eq "storage_host") {
push @sharedlans, $virtlan;
}
}
}
# Round 2: re-assign addresses using the IP buddy allocator, via
# its testbed wrapper. Note that this amends the addresses assigned
# in the virt_lans table as well.
if (@sharedlans) {
# Don't pull this module in until now to avoid unnecessary
# dependencies. If we find ourselves using it in other places,
# then move this up to package scope.
require IPBuddyWrapper;
# Tie this buddy allocator to the "storage_host" global address range.
my $buddy = IPBuddyWrapper->new("storage_host");
return -1
unless defined($buddy);
# This call will lock the "reserved_addresses" table.
$buddy->lock()
or return -1;
# Now preload the reservations in the DB, including those associated
# with this experiment. The buddy allocator will allocate around
# these.
$buddy->loadReservedRanges($self->virtexperiment()) == 0
or return -1;
foreach my $virtlan (@sharedlans) {
my $vlanname = $virtlan->vname();
my $mask = $virtlan->_mask();
my $range = $buddy->requestAddressRange($self->virtexperiment(),
$mask);
if (!defined($range)) {
$buddy->unlock();
return -1;
}
$self->printdb("Reserved shared address range for ".
"$vlanname: $range/$mask");
# Got a new reserved range, so re-IP this lan.
foreach my $member ($virtlan->memberlist()) {
# Will this happen?
next
if (!exists($self->solution_portmap()->{$member}));
# Grab some info/objects for the current lan member.
my $ip = $member->ip();
my $iface = $self->solution_portmap()->{$member};
my $vnodename = $member->vnode();
# re-IP this member. Push back into the virt_lans
# table; otherwise, things that expect the IP set
# there to match what is set in the
# interfaces/vinterfaces table will go wonky.
my $new_ip = $buddy->getNextAddress($range);
if (!defined($new_ip)) {
$buddy->unlock();
return -1;
}
# XXX: this stuff may not work...
$member->ip($new_ip);
my $sflags = 0;
$sflags |= $self->verbose() ?
VirtExperiment::STORE_FLAGS_DEBUG :
0;
$sflags |= $self->impotent() ?
VirtExperiment::STORE_FLAGS_IMPOTENT :
0;
if ($member->Store($sflags) != 0) {
$buddy->unlock();
return -1;
}
}
}
# All done with reservations, so commit and unlock.
$buddy->commitReservations()
unless $self->impotent();
$buddy->unlock();
}
return 0;
}
#
# Write the vlans table to the DB.
#
......
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