Commit 7b8b84aa authored by Jonathon Duerig's avatar Jonathon Duerig
Browse files

Added a more flexible framework surrounding routing and ip assignment. Added routing.

parent 00b8afed
......@@ -12,7 +12,8 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@
include $(OBJDIR)/Makeconf
SUBDIRS = src
SUBDIRS =
# src
#
# Force dependencies on the scripts so that they will be rerun through
......
ipassign requires the METIS library.
This is compiled with the rest of Emulab build process. If you wish to run automated regression testing, you will have to build that seperately. Simply compile src/autocheck.cc into bin/autocheck and compile src/boolcmp.cc into bin/boolcmp and then run bin/autocheck. Make sure that you are in the ipassign directory so that autocheck can find the configuration file in etc/autocheck.conf
Currently, ipassign is not compiled with the rest of Emulab. Until integration
happens, run 'compile.sh' in the 'tbsetup/ipassign' directory. The binaries
will be in 'tbsetup/ipassign/bin'
Command Line Options
--------------------
None yet. This program reads from the standard input and writes to the
standard output.
This program reads from the standard input. Normally, only standard output is
written to. If there is an error, standard error is written to. Currently,
certain check results and progress messages are printed to standard error
as well. When the program is integrated with the rest of Emulab, these will
be removed.
Note that for now, the command line option should include '-c ', then either '-s ' or '-p<number> ', then one of '-h ', '-l ', or '-n '.
Example: "bin/ipassign -c -p2 -n < graph/pastry44.graph"
Use conservative bitspace allocation for 2 partitions with
host-to-network routing on the pastry44 graph.
Example: "bin/ipassign -c -s -h < graph/smalltern.graph"
Use conservative bitspace allocation for square root of the # of LANs
partitions with host-host routing on the smalltern graph
'-p#', '-s', '-b', and '-g' modify the same behaviour. If more than one is
used, the last one used takes precedence.
'-p#' When calculating assignment for ip addresses, # is used to set how
many partitions to create. Example: '-p20' would create 20 partitions.
'-s' When calculating the number of partitions to use for ip assignment,
use the square root of the number of LANs. (default)
#'-b' Instead of using a single level of hierachy in which a set number
# of partitions are used, cut the graph in half assigning each
# half a zero or one, cut each of the resulting subgraphs in half,
# etc. until the graph has been fully partitioned.
# -- NOT IMPLEMENTED --
#'-g' Partition the graph using a greedy marriage algorithm instead of
# METIS.
# -- NOT IMPLEMENTED --
!!! Always use this for now. Dynamic bitspace allocation is not yet
implemented !!!
'-c' Use conservative bitspace allocation. Each level of the hierarchy
is allocated a fixed number of bits. Only takes effect alongside
of -p or -s
'-h', '-l', and '-n' modify the same behaviour. If more than one is used, the
last one used takes precedence.
'-h' When calculating the routing tables, use host-to-host routing. This
will create one table entry for every host in the network.
'-l' Calculate routing using host-lan routing. This will create a table
entry for each LAN in the network.
Note that there is a bug here which causes circular routes on large
topologies. I am tracking it down.
'-n' Host-to-net routing. Calculate routes using a hierarchical network
number if possible. Otherwise use a LAN. Theoretically, this should
produce the smallest number of routes in each routing table. (default)
-- NOT IMPLEMENTED --
Input
-----
The input consists of a series of specifications for LANs (a link is just a
LAN with only two nodes). Each LAN is specified by a number representing the
weight (this must be integral), followed by a series of integers, each one
representing a node which the LAN is connected to. The LANs are automatically
numbered in order of appearance (starting at 0).
LAN with only two nodes). Each LAN is specified by the number of bits used
to represent that LAN, number representing the weight (this must be integral),
followed by a series of integers, each one representing a node which the LAN
is connected to. The LANs are automatically numbered in order of appearance
(starting at 0). Note that the number of bits is only used for dynamic IP
assignment, which is not yet implemented. Therefore the first number is
ignored for now.
<weight> <node> <node> [node [...]]
<bits> <weight> <node> <node> [node [...]]
Example graph:
1 0 1 2
1 5 2 3
8 1 0 1
8 1 0 2
8 1 1 2
This graph would contain three LANs. The first has a weight of 1, and is
connected to nodes 0 and 1. The second is also of weight 1 and is
connected to nodes 0 and 2. The third is weight 1 and connects nodes 1 and 2.
This graph would contain two LANs. The first has a weight of one, and is
connected to nodes 0, 1, and 2. The second is also of weight 1 and is
connected to nodes 5, 2, and 3.
Nodes should be numbered starting at 0 and every number up to (numNodes - 1)
should have an associated node.
Output
------
The first line of the output is a header. It consists of three integers,
representing the number of bits allocated for the global network, the number
of bits for the LANs in each network, and the number of bits for the hosts in
each LAN, respectively. This allows the outside to calculate bitmasks for
every address.
Output is divided into two sections. The first section associates each node-lan
connection pair with an IP address. The second section, delimited by '%%' on
a line by itself, shows the routing table associated with each node.
<route-file> := <ip-section> "%%\n" <route-section>
<ip-section> := <ip-line>*
The bitmask for a global net is 0xFFFFFFFF << (lanBits + hostBits).
The bitmask for a lan net is (0xFFFFFFFF >> hostBits) << hostBits.
The bitmask for a host is 0xFFFFFFFF.
<ip-line> := <LAN#> " " <node#> " " <IP-address> "\n"
Each additional line of output is a specification of an IP address. There
are two numbers and an IP dotted quadruplet. The first number is the LAN
number (which is the ordinality of that LAN - 1). The second is the number
of a node.
<route-section> := <route-table>*
This means that the node has the IP address on the interface connected with
the LAN.
<route-table> := "Routing table for node: " <node#> "\n" <route>* "%%\n"
<LAN> <node> <IP>
<route> := "Destination: " <IP-address> "/" <netmask-size>
" FirstHop: " <IP-address> "\n"
Example output:
An example of the output for the above graph for host-host routing would be:
0 0 10.0.0.1
0 1 10.0.0.2
0 2 10.0.0.3
1 2 10.0.1.1
1 3 10.0.1.2
1 5 10.0.1.3
1 0 10.0.1.1
1 2 10.0.1.2
2 1 10.0.2.1
2 2 10.0.2.2
%%
Routing table for node: 0
Destination: 10.0.2.1/32 FirstHop: 10.0.0.2
Destination: 10.0.2.2/32 FirstHop: 10.0.1.2
%%
Routing table for node: 1
Destination: 10.0.1.1/32 FirstHop: 10.0.0.1
Destination: 10.0.1.2/32 FirstHop: 10.0.2.2
%%
Routing table for node: 2
Destination: 10.0.0.1/32 FirstHop: 10.0.1.1
Destination: 10.0.0.2/32 FirstHop: 10.0.2.1
%%
If there was an error, this program returns 1, otherwise it returns 0.
Process
-------
The input is used to populate a graph object. This object contains many
LANs, each LAN being a node in the graph. It is important to realize that
the graph the program is interested in is a graph of a bunch of LANs connected
to each other by nodes. A kind of inverse of the graph we normally think of.
The reason that we care about LANs in nets rather than nodes is because each
interface of a node can be part of a different net, whereas every interface
in a LAN should be part of the same net.
This LAN-graph is then converted into an appropriate form and fed into the
METIS graph-partitioning algorithm. The result is that each LAN is given
a partition number from 0 to numPartitions-1. There is a slight inconsistency
if numPartitions == 1. METIS changes every LAN's partition number to 1 rather
than 0. This is accounted for and each node is set to 0 in this case.
Then IP addresses are generated. The global-net number of each node is the
partition number. For each global-net number, successive LANs in that
global-net are given LAN-net numbers. And for each host in a LAN, successive
host numbers are given. Host numbers that are all 0s or all 1s are avoided.
Framework -- The framework processes the command line arguments and uses
the results to figure out what kind of ip assignment and routing
to do. Then it takes care of populating their inputs and acts
as an intermediary for feeding the results of ip assignment
into the router.
ConservativeAssigner --
HostRouter --
LanRouter --
NetRouter --
Errors
------
Non-numeric and non-whitespace characters in the input are invalid.
LANs must have at least two nodes connected to them.
Right now the size of the bitspace used for hosts and LANs is pretty
conservative. The largest network defines the space for LANs and the largest
LAN defines the space for hosts. Therefore with large graphs, it is possible
to run out of bitspace, which is an error.
Input graphs should be connected
Room for Improvement
--------------------
One could be a lot smarter about how to allocate which bits for which level
of the hierarchy. If there are a few large lans, for instance, it is more
bit-conservative to assign them multiple blocks rather than increase the
block size.
One could increase the number of levels in the hierarchy. Right now there
are only 3. If this were increased, one might be able to create even
smaller routing tables.
Bugs
----
None that I know of.
In the LAN-router, circular topologies are generated for large graphs. I am
investigating it. This is not that critical as LAN routing is not used
frequently.
#!/bin/sh
export EXTRA_LIB_PATH=/users/duerig
#g++ -O3 -c -o tmp/ipassign.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/ipassign.cc
#g++ -O3 -c -o tmp/bitmath.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/bitmath.cc
#g++ -O3 -c -o tmp/Assigner.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/Assigner.cc
#g++ -O3 -c -o tmp/ConservativeAssigner.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/ConservativeAssigner.cc
#g++ -O3 -c -o tmp/Framework.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/Framework.cc
#g++ -O3 -c -o tmp/HostRouter.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/HostRouter.cc
#g++ -O3 -c -o tmp/Router.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/Router.cc
#g++ -O3 -c -o tmp/LanRouter.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/LanRouter.cc
#g++ -O3 -c -o tmp/NetRouter.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/NetRouter.cc
g++ -O3 -c -o tmp/coprocess.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/coprocess.cc
g++ -O3 -o bin/ipassign -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ -L${EXTRA_LIB_PATH}/metis/metis-4.0 tmp/ipassign.o tmp/bitmath.o tmp/Assigner.o tmp/ConservativeAssigner.o tmp/Framework.o tmp/HostRouter.o tmp/Router.o tmp/LanRouter.o tmp/NetRouter.o tmp/coprocess.o -lmetis -lm
g++ -O3 -c -o tmp/routestat.o -I${EXTRA_LIB_PATH}/metis/metis-4.0/Lib/ src/routestat.cc
g++ -O3 -o bin/routestat -L${EXTRA_LIB_PATH}/metis/metis-4.0 tmp/routestat.o tmp/bitmath.o -lmetis -lm
#g++ -O3 -o bin/routecalc src/routecalc.cc
#g++ -o bin/inet2graph src/inet2graph.cc -lm
#g++ -o bin/brite2graph src/brite2graph.cc -lm
#g++ -o bin/top2graph src/top2graph.cc -lm
#g++ -o bin/autocheck src/autocheck.cc -lm
#g++ -o bin/boolcmp src/boolcmp.cc -lm
#bin/autocheck
#27338.150u 429.446s 12:16:11.56 62.8% 430+-267k 1+2584io 1998215pf+0w
# Lines starting with a '#' are comments and are ignored.
# Lines starting with '=' mark a divider. A horizontal line is written at
# that point. Anything else on the line is ignored.
# Lines starting with a '$' define a variable. Paths and filenames are
# relative to the working directory of autocheck. Slashes are
# automatically converted to be compatible with your os.
# All of the paths default to '.'
# Where to put the output generated by the script. This output file is
# checked against the output file with the same name in $keyOutputPath.
$trialOutputPath tmp/result/
# This is the path where the test scripts are held.
$scriptPath graph/
# This is where the master copies of the script output files are held.
$keyOutputPath result/
### The three command variables must have a value.
# This is the command that is used to execute scripts.
$runCommand bin/ipassign
# This is the command that is used to compare two output or two program files.
$compareCommand bin/boolcmp
# Every other line must contain a word, some whitespace, then a number.
# For example (if the next line didn't have the '#'):
# word 15
# For each value from 1 to the number, a test will be run and the following
# files will be created (relative to the current directory):
# $trialOutputPath/word1.result
# And so on for 2..14
# $trialOutputPath/word14-output.txt
# After those files are created, then comparisons are made to determine
# whether the test passes or failed.
# If '$trialOutputPath/word?.result' is identical to
# '$keyOutputPath/word?.result', then the output test passes.
# Where ? means the number of the test (between 1 and 15 in this case)
# Note that this means that both word and number should only contain
# characters that can be put in a filename. And the number should
# be an integer.
# Note if the 'zero index' flag is set, then the number range would be
# 0..14 in this example.
# word number
# ---- ------
=
kgraph 5
=
torus-real 1
=
narrows 9
=
tree 3
=
1 0 1
1 0 2
1 1 2
0 1 0 1
0 1 0 2
0 1 1 2
1 0 1
1 0 2
1 0 3
1 1 2
1 1 3
1 2 3
0 1 0 1
0 1 0 2
0 1 0 3
0 1 1 2
0 1 1 3
0 1 2 3
// Assigner.cc
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2003 University of Utah and the Flux Group.
* All rights reserved.
*/
#include "lib.h"
#include "Exception.h"
#include "Assigner.h"
Assigner::~Assigner()
{
}
void Assigner::setPartitionCount(size_t)
{
}
// Assigner.h
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2003 University of Utah and the Flux Group.
* All rights reserved.
*/
// This is the base class for all ip assignment algorithms. Using
// this class, the ip assignment algorithm can be selected dynamically.
#ifndef ASSIGNER_H_IP_ASSIGN_2
#define ASSIGNER_H_IP_ASSIGN_2
class Assigner
{
public:
typedef std::vector< std::vector<size_t> > NodeLookup;
typedef std::vector<int> MaskTable;
typedef std::vector<IPAddress> PrefixTable;
typedef std::vector< std::vector< size_t > > LevelLookup;
public:
virtual ~Assigner();
virtual std::auto_ptr<Assigner> clone(void) const=0;
// Determines how many partitions to divide the graph into.
// Should only affect NumberAssigner and ConservativeAssigner
virtual void setPartitionCount(size_t);
// This is called repeatedly to form the graph. Raw parsing
// of the command line is done outside.
// Any numbers between [0..MaxNodeNumber] that are unused
// should be represented with the constant INVALID_NODE
virtual void addLan(int, int, std::vector<size_t>)=0;
// The main processing function. After the graph has been populated,
// assign IP addresses to the nodes.
virtual void ipAssign(void)=0;
// Output the network and associated IP addresses to the specified stream.
virtual void print(std::ostream &) const=0;
// Populate the argument vectors with our state so that routes can
// be calculated.
// for the explanation of these arguments, see Router.h
virtual void graph(std::vector<NodeLookup> & nodeToLevel,
std::vector<MaskTable> & levelMaskSize,
std::vector<PrefixTable> & levelPrefix,
std::vector<LevelLookup> & levelMakeup,
std::vector<int> & lanWeights) const=0;
private:
};
#endif
This diff is collapsed.
// ConservativeAssigner.h
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2003 University of Utah and the Flux Group.
* All rights reserved.
*/
// This ip assignment algorithm divides the graph into a set number of
// partitions. It uses conservative bit assignment rather than dynamic
// bit assignment.
#ifndef CONSERVATIVE_ASSIGNER_H_IP_ASSIGN_2
#define CONSERVATIVE_ASSIGNER_H_IP_ASSIGN_2
#include "Assigner.h"
class ConservativeAssigner : public Assigner
{
private:
struct Lan
{
Lan()
: weight(0), partition(0), number(0), ip(0)
{
}
size_t number;
int weight;
int partition;
IPAddress ip;
std::vector<size_t> nodes;
};
public:
ConservativeAssigner();
virtual ~ConservativeAssigner();
virtual std::auto_ptr<Assigner> clone(void) const;
virtual void setPartitionCount(size_t newCount);
virtual void addLan(int bits, int weight, std::vector<size_t> nodes);
virtual void ipAssign(void);
virtual void print(std::ostream & output) const;
virtual void graph(std::vector<NodeLookup> & nodeToLevel,
std::vector<MaskTable> & levelMaskSize,
std::vector<PrefixTable> & levelPrefix,
std::vector<LevelLookup> & levelMakeup,
std::vector<int> & lanWeights) const;
private:
// populate arguments with the METIS graph format
void convert(std::vector<int> & indexes, std::vector<int> & neighbors,
std::vector<int> & weights) const;
// Given a lan, add the appropriate adjacency and weight information
// to arrays.
void convertAddLan(Lan const & lanToAdd, std::vector<int> & neighbors,
std::vector<int> & weights) const;
// Given the number of a node, calculate the adjacencies and weights
// and put them into arrays.
void convertAddNode(Lan const & info, int currentNode,
std::vector<int> & neighbors,
std::vector<int> & weights) const;
static int getBits(int num);
static int getLanBits(std::vector<int> const & partitionArray,
int numPartitions);
static int roundUp(double num);
static void checkBitOverflow(int numBits);
void assignNumbers(int networkSize, int lanSize, int hostSize,
int numPartitions);
bool isConnected(int whichPartition);
void makeConnected(int whichPartition);
private:
std::vector<Lan> m_lanList;
NodeLookup m_nodeToLan;
int m_partitionCount;
int m_largestLan;
int m_lanMaskSize;
int m_netMaskSize;
PrefixTable m_partitionIPList;
};
#endif
......@@ -26,19 +26,27 @@ public:
: message(error)
{
}
virtual const char* what() const throw()
virtual char const * what() const throw()
{
return message.c_str();
}
virtual void addToMessage(char const * addend)
{
message += addend;
}
virtual void addToMessage(string const & addend)
{
addToMessage(addend.c_str());
}
private:
string message;
};
class BitOverflowError : public StringException
class BitOverflowException : public StringException
{
public:
explicit BitOverflowError(std::string const & error)
: StringException(error)
explicit BitOverflowException(std::string const & error)
: StringException("Too many hosts and lans: " + error)
{
}
};
......@@ -47,16 +55,16 @@ class InvalidCharacterException : public StringException
{
public:
explicit InvalidCharacterException(std::string const & error)
: StringException(error)
: StringException("Invalid character(s) in line: " + error)
{
}
};
class EmptyLineException : public StringException
class MissingWeightException : public StringException
{
public:
explicit EmptyLineException(std::string const & error)
: StringException(error)
explicit MissingWeightException(std::string const & error)
: StringException("Missing weight in line: " + error)
{
}
};
......@@ -65,12 +73,66 @@ class NotEnoughNodesException : public StringException
{
public:
explicit NotEnoughNodesException(std::string const & error)
: StringException(error)
: StringException("Not enough nodes in line: " + error)
{
}
};
class NoHeaderException : public StringException
{
public:
explicit NoHeaderException(std::string const & error)
: StringException("Error Reading Header: " + error)
{
}
};
class InvalidArgumentException : public StringException
{
public:
explicit InvalidArgumentException(std::string const & error)
: StringException("Invalid Argument: " + error)
{
}
};
class ImpossibleConditionException : public StringException
{
public:
explicit ImpossibleConditionException(std::string const & error)
: StringException("Impossible Condition in Function: " + error)
{
}
};
class NoConnectionException : public StringException