Attempt at revised/simplified topo class:

- keys are strings
- metadata is simply a dict
- buildFromTopo greatly simplified
This commit is contained in:
Bob Lantz
2012-03-20 00:17:30 -07:00
parent 318ae55e35
commit 5a8bb48951
4 changed files with 188 additions and 393 deletions
+42 -56
View File
@@ -97,7 +97,7 @@ from mininet.log import info, error, debug, output
from mininet.node import Host, OVSKernelSwitch, Controller
from mininet.link import Link
from mininet.util import quietRun, fixLimits
from mininet.util import macColonHex, ipStr, ipParse, netParse
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
from mininet.term import cleanUpScreens, makeTerms
class Mininet( object ):
@@ -131,6 +131,8 @@ class Mininet( object ):
self.link = link
self.intf = intf
self.ipBase = ipBase
self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
self.nextIP = 1 # start for address allocation
self.inNamespace = inNamespace
self.xterms = xterms
self.cleanup = cleanup
@@ -143,7 +145,6 @@ class Mininet( object ):
self.controllers = []
self.nameToNode = {} # name to Node (Host/Switch) objects
self.idToNode = {} # dpid to Node (Host/Switch) objects
self.terms = [] # list of spawned xterm processes
@@ -153,35 +154,39 @@ class Mininet( object ):
if topo and build:
self.build()
# BL Note:
# The specific items for host/switch/etc. should probably be
# handled in the node classes rather than here!!
def addHost( self, name, host=None, **params ):
def addHost( self, name, cls=None, **params ):
"""Add host.
name: name of host to add
host: custom host constructor (optional)
cls: custom host class/constructor (optional)
params: parameters for host
returns: added host"""
if not host:
host = self.host
h = host( name, **params)
# Default IP and MAC addresses
defaults = { 'ip': ipAdd( self.nextIP,
ipBaseNum=self.ipBaseNum,
prefixLen=self.prefixLen ) }
if self.autoSetMacs:
defaults[ 'mac'] = macColonHex( self.nextIP )
self.nextIP += 1
defaults.update( params )
if not cls:
cls = self.host
h = cls( name, **defaults )
self.hosts.append( h )
self.nameToNode[ name ] = h
return h
def addSwitch( self, name, switch=None, **params ):
def addSwitch( self, name, cls=None, **params ):
"""Add switch.
name: name of switch to add
switch: custom switch constructor (optional)
cls: custom switch class/constructor (optional)
returns: added switch
side effect: increments listenPort ivar ."""
defaults = { 'listenPort': self.listenPort,
'inNamespace': self.inNamespace }
defaults.update( params )
if not switch:
switch = self.switch
sw = self.switch( name, **defaults )
if not cls:
cls = self.switch
sw = cls( name, **defaults )
if not self.inNamespace and self.listenPort:
self.listenPort += 1
self.switches.append( sw )
@@ -199,6 +204,15 @@ class Mininet( object ):
self.nameToNode[ name ] = controller_new
return controller_new
def addLink( self, src, dst, srcPort=None, dstPort=None,
cls=None, **params ):
"Add a link from topo"
if self.intf and not 'intf' in params:
params[ 'intf' ] = self.intf
if not cls:
cls = self.link
return cls( src, dst, srcPort, dstPort, **params )
def configHosts( self ):
"Configure a set of hosts."
for host in self.hosts:
@@ -215,40 +229,6 @@ class Mininet( object ):
At the end of this function, everything should be connected
and up."""
ipBaseNum, prefixLen = netParse( self.ipBase )
if not topo:
topo = self.topo()
def addNode( prefix, addMethod, nodeId ):
"Add a host or a switch from topo"
name = prefix + topo.name( nodeId )
ni = topo.nodeInfo( nodeId )
# Default IP and MAC addresses
defaults = { 'ip': topo.ip( nodeId,
ipBaseNum=ipBaseNum,
prefixLen=prefixLen ) }
if self.autoSetMacs:
defaults[ 'mac'] = macColonHex( nodeId )
defaults.update( ni.params )
node = addMethod( name, cls=ni.cls, **defaults )
self.idToNode[ nodeId ] = node
info( name + ' ' )
def addLink( srcId, dstId, link=None ):
"Add a link from topo"
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
srcPort, dstPort = topo.port( srcId, dstId )
ei = topo.edgeInfo( srcId, dstId )
link = getattr( ei, 'cls', link )
params = ei.params
if self.intf and not 'intf' in params:
params[ 'intf' ] = self.intf
if not link:
link = self.link
info( '(%s, %s) ' % ( src.name, dst.name ) )
link( src, dst, srcPort, dstPort, **params )
# Possibly we should clean up here and/or validate
# the topo
if self.cleanup:
@@ -262,16 +242,22 @@ class Mininet( object ):
self.addController( 'c0' )
info( '*** Adding hosts:\n' )
for hostId in sorted( topo.hosts() ):
addNode( 'h', self.addHost, hostId )
for hostName in topo.hosts():
self.addHost( hostName, **topo.nodeInfo( hostName ) )
info( hostName + ' ' )
info( '\n*** Adding switches:\n' )
for switchId in sorted( topo.switches() ):
addNode( 's', self.addSwitch, switchId )
for switchName in topo.switches():
self.addSwitch( switchName, **topo.nodeInfo( switchName) )
info( switchName + ' ' )
info( '\n*** Adding links:\n' )
for srcId, dstId in sorted( topo.edges() ):
addLink( srcId, dstId )
for srcName, dstName in topo.links(sort=True):
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
srcPort, dstPort = topo.port( srcName, dstName )
self.addLink( src, dst, srcPort, dstPort,
**topo.linkInfo( srcName, dstName ) )
info( '(%s, %s) ' % ( src.name, dst.name ) )
info( '\n' )
+114 -317
View File
@@ -16,146 +16,73 @@ setup for testing, and can even be emulated with the Mininet package.
# from networkx.classes.graph import Graph
from networkx import Graph
from mininet.util import netParse, ipStr
class NodeID(object):
'''Topo node identifier.'''
def __init__(self, dpid = None):
'''Init.
@param dpid dpid
'''
# DPID-compatible hashable identifier: opaque 64-bit unsigned int
self.dpid = dpid
def __str__(self):
'''String conversion.
@return str dpid as string
'''
return str(self.dpid)
def name_str(self):
'''Name conversion.
@return name name as string
'''
return str(self.dpid)
def ip_str(self, ipBase=None, prefixLen=8, ipBaseNum=0x0a000000):
'''Name conversion.
ipBase: optional base IP address string
prefixLen: optional IP prefix length
ipBaseNum: option base IP address as int
@return ip ip as string
'''
if ipBase:
ipnum, prefixLen = netParse( ipBase )
else:
ipBaseNum = ipBaseNum
# Ugly but functional
assert self.dpid < ( 1 << ( 32 - prefixLen ) )
mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
ipnum = self.dpid + ( ipBaseNum & mask )
return ipStr( ipnum )
class Node( object ):
'''Node-specific vertex metadata for a Topo object.'''
def __init__(self, connected=False, admin_on=True,
power_on=True, fault=False, is_switch=True,
cls=None, **params ):
'''Init.
@param connected actively connected to controller
@param admin_on administratively on or off
@param power_on powered on or off
@param fault fault seen on node
@param is_switch switch or host
@param cls node class (e.g. Host, Switch)
@param params node parameters
'''
self.connected = connected
self.admin_on = admin_on
self.power_on = power_on
self.fault = fault
self.is_switch = is_switch
# BL: Above should mostly be deleted and replaced by the following
# is_switch is a bit annoying if we are already specifying
# a switch class!! Except that if cls is not specified,
# then Mininet() knows whether to create a switch or a host
# node and can call its own constructors...
self.cls = cls
self.params = params
class Edge(object):
'''Edge-specific metadata for a StructuredTopo graph.'''
def __init__(self, admin_on=True, power_on=True, fault=False,
cls=None, **params):
'''Init.
@param admin_on administratively on or off; defaults to True
@param power_on powered on or off; defaults to True
@param fault fault seen on edge; defaults to False
'''
self.admin_on = admin_on
self.power_on = power_on
self.fault = fault
# Above should be deleted and replaced by the following
self.cls = cls
self.params = params
from mininet.util import netParse, ipStr, irange, natural, naturalSeq
class Topo(object):
"""Data center network representation for structured multi-trees.
Note that the order of precedence is:
per-node/link classes and parameters
per-topo classes
per-network classes"""
"Data center network representation for structured multi-trees."
def __init__(self, node=None, switch=None, link=None ):
"""Create Topo object.
node: default node/host class (optional)
switch: default switch class (optional)
link: default link class (optional)
ipBase: default IP address base (optional)"""
def __init__(self, hopts=None, sopts=None, lopts=None):
"""Topo object:
hinfo: default host options
sopts: default switch options
lopts: default link options"""
self.g = Graph()
self.node_info = {} # dpids hash to Node objects
self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects
self.node_info = {}
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
self.hopts = {} if hopts is None else hopts
self.sopts = {} if sopts is None else lopts
self.lopts = {} if lopts is None else lopts
self.ports = {} # ports[src][dst] is port on src that connects to dst
self.id_gen = NodeID # class used to generate dpid
self.node = node
self.switch = switch
self.link = link
def add_node(self, dpid, node=None):
'''Add Node to graph.
def add_node(self, name, *args, **opts):
"""Add Node to graph.
add_node('name', dict) <or> add_node('name', **opts)
name: name
args: dict of node options
opts: node options"""
self.g.add_node(name)
if args and type(args[0]) is dict:
opts = args[0]
self.node_info[name] = opts
return name
@param dpid dpid
@param node Node object
'''
self.g.add_node(dpid)
if not node:
node = Node( link=self.link )
self.node_info[dpid] = node
def add_host(self, name, *args, **opts):
"""Convenience method: Add host to graph.
add_host('name', dict) <or> add_host('name', **opts)
name: name
args: dict of node options
opts: node options"""
if not opts and self.hopts:
opts = self.hopts
return self.add_node(name, *args, **opts)
def add_edge(self, src, dst, edge=None):
'''Add edge (Node, Node) to graph.
def add_switch(self, name, **opts):
"""Convenience method: Add switch to graph.
add_switch('name', dict) <or> add_switch('name', **opts)
name: name
args: dict of node options
opts: node options"""
if not opts and self.sopts:
opts = self.sopts
result = self.add_node(name, is_switch=True, **opts)
return result
@param src src dpid
@param dst dst dpid
@param edge Edge object
'''
src, dst = tuple(sorted([src, dst]))
def add_link(self, src, dst, *args, **opts):
"""Add link (Node, Node) to topo.
add_link(src, dst, dict) <or> add_link(src, dst, **opts)
src: src name
dst: dst name
args: dict of node options
params: link parameters"""
src, dst = sorted([src, dst], key=naturalSeq)
self.g.add_edge(src, dst)
if not edge:
edge = Edge( cls=self.link )
self.edge_info[(src, dst)] = edge
if args and type(args[0]) is dict:
opts = args[0]
if not opts and self.sopts:
opts = self.sopts
self.link_info[(src, dst)] = opts
self.add_port(src, dst)
return src, dst
def add_port(self, src, dst):
'''Generate port mapping for new edge.
@@ -175,131 +102,48 @@ class Topo(object):
if src not in self.ports[dst]:
# num outlinks
self.ports[dst][src] = len(self.ports[dst]) + dst_base
def node_enabled(self, dpid):
'''Is node connected, admin on, powered on, and fault-free?
@param dpid dpid
@return bool node is enabled
'''
ni = self.node_info[dpid]
return ni.connected and ni.admin_on and ni.power_on and not ni.fault
def nodes_enabled(self, dpids, enabled = True):
'''Return subset of enabled nodes
@param dpids list of dpids
@param enabled only return enabled nodes?
@return dpids filtered list of dpids
'''
if enabled:
return [n for n in dpids if self.node_enabled(n)]
def nodes(self, sort=True):
"Return nodes in graph"
if sort:
return sorted( self.g.nodes(), key=natural )
else:
return dpids
def nodes(self, enabled = True):
'''Return graph nodes.
@param enabled only return enabled nodes?
@return dpids list of dpids
'''
return self.nodes_enabled(self.g.nodes(), enabled)
def nodes_str(self, dpids):
'''Return string of custom-encoded nodes.
@param dpids list of dpids
@return str string
'''
return [str(self.id_gen(dpid = dpid)) for dpid in dpids]
return self.g.nodes()
def is_switch(self, n):
'''Returns true if node is a switch.'''
return self.node_info[n].is_switch
info = self.node_info[n]
return info and info['is_switch']
def switches(self, enabled = True):
def switches(self, sort=True):
'''Return switches.
@param enabled only return enabled nodes?
sort: sort switches alphabetically
@return dpids list of dpids
'''
nodes = [n for n in self.g.nodes() if self.is_switch(n)]
return self.nodes_enabled(nodes, enabled)
return [n for n in self.nodes(sort) if self.is_switch(n)]
def hosts(self, enabled = True):
def hosts(self, sort=True):
'''Return hosts.
@param enabled only return enabled nodes?
sort: sort hosts alphabetically
@return dpids list of dpids
'''
return [n for n in self.nodes(sort) if not self.is_switch(n)]
def is_host(n):
'''Returns true if node is a host.'''
return not self.node_info[n].is_switch
nodes = [n for n in self.g.nodes() if is_host(n)]
return self.nodes_enabled(nodes, enabled)
def edge_enabled(self, edge):
'''Is edge admin on, powered on, and fault-free?
@param edge (src, dst) dpid tuple
@return bool edge is enabled
def links(self, sort=True):
'''Return links.
sort: sort links alphabetically
@return links list of name pairs
'''
src, dst = edge
src, dst = tuple(sorted([src, dst]))
ei = self.edge_info[tuple(sorted([src, dst]))]
return ei.admin_on and ei.power_on and not ei.fault
def edges_enabled(self, edges, enabled = True):
'''Return subset of enabled edges
@param edges list of edges
@param enabled only return enabled edges?
@return edges filtered list of edges
'''
if enabled:
return [e for e in edges if self.edge_enabled(e)]
if not sort:
return self.g.edges()
else:
return edges
def edges(self, enabled = True):
'''Return edges.
@param enabled only return enabled edges?
@return edges list of dpid pairs
'''
return self.edges_enabled(self.g.edges(), enabled)
def edges_str(self, dpid_pairs):
'''Return string of custom-encoded node pairs.
@param dpid_pairs list of dpid pairs (src, dst)
@return str string
'''
edges = []
for pair in dpid_pairs:
src, dst = pair
src = str(self.id_gen(dpid = src))
dst = str(self.id_gen(dpid = dst))
edges.append((src, dst))
return edges
return sorted( self.g.edges(), key=naturalSeq )
def port(self, src, dst):
'''Get port number.
@param src source switch DPID
@param dst destination switch DPID
@param src source switch name
@param dst destination switch name
@return tuple (src_port, dst_port):
src_port: port on source switch leading to the destination switch
dst_port: port on destination switch leading to the source switch
@@ -308,84 +152,41 @@ class Topo(object):
assert dst in self.ports and src in self.ports[dst]
return (self.ports[src][dst], self.ports[dst][src])
def edgeInfo( self, src, dst ):
"Return edge metadata"
# BL: Perhaps this should be rethought or we should just use the
# dicts...
return self.edge_info[ ( src, dst ) ]
def linkInfo( self, src, dst ):
"Return link metadata"
src, dst = sorted((src, dst), key=naturalSeq)
return self.link_info[(src, dst)]
def enable_edges(self):
'''Enable all edges in the network graph.
def nodeInfo( self, name ):
"Return metadata (dict) for node"
info = self.node_info[ name ]
return info if info is not None else {}
Set admin on, power on, and fault off.
'''
for e in self.g.edges():
src, dst = e
ei = self.edge_info[tuple(sorted([src, dst]))]
ei.admin_on = True
ei.power_on = True
ei.fault = False
def enable_nodes(self):
'''Enable all nodes in the network graph.
Set connected on, admin on, power on, and fault off.
'''
for node in self.g.nodes():
ni = self.node_info[node]
ni.connected = True
ni.admin_on = True
ni.power_on = True
ni.fault = False
def enable_all(self):
'''Enable all nodes and edges in the network graph.'''
self.enable_nodes()
self.enable_edges()
def name(self, dpid):
'''Get string name of node ID.
@param dpid DPID of host or switch
@return name_str string name with no dashes
'''
return self.id_gen(dpid = dpid).name_str()
def ip(self, dpid, **params):
'''Get IP dotted-decimal string of node ID.
@param dpid DPID of host or switch
@param params: params to pass to ip_str
@return ip_str
'''
return self.id_gen(dpid = dpid).ip_str(**params)
def nodeInfo( self, dpid ):
"Return metadata for node"
# BL: may wish to rethink this or just use dicts..
return self.node_info[ dpid ]
def setNodeInfo( self, name, info ):
self.node_info[ name ] = info
@staticmethod
def sorted( items ):
"Items sorted in natural (i.e. alphabetical) order"
return sorted(items, key=natural)
class SingleSwitchTopo(Topo):
'''Single switch connected to k hosts.'''
def __init__(self, k = 2, enable_all = True):
def __init__(self, k=2, **opts):
'''Init.
@param k number of hosts
@param enable_all enables all nodes and switches?
'''
super(SingleSwitchTopo, self).__init__()
super(SingleSwitchTopo, self).__init__(**opts)
self.k = k
self.add_node(1, Node())
hosts = range(2, k + 2)
for h in hosts:
self.add_node(h, Node(is_switch = False))
self.add_edge(h, 1, Edge())
if enable_all:
self.enable_all()
switch = self.add_switch('s1')
for h in irange(1, k):
host = self.add_host('h%s' % h)
self.add_link(host, switch)
class SingleSwitchReversedTopo(SingleSwitchTopo):
@@ -422,27 +223,23 @@ class SingleSwitchReversedTopo(SingleSwitchTopo):
class LinearTopo(Topo):
'''Linear topology of k switches, with one host per switch.'''
"Linear topology of k switches, with one host per switch."
def __init__(self, k = 2, enable_all = True):
'''Init.
def __init__(self, k=2, **opts):
"""Init.
k: number of switches (and hosts)
hconf: host configuration options
lconf: link configuration options"""
@param k number of switches (and hosts too)
@param enable_all enables all nodes and switches?
'''
super(LinearTopo, self).__init__()
super(LinearTopo, self).__init__(**opts)
self.k = k
switches = range(1, k + 1)
for s in switches:
h = s + k
self.add_node(s, Node())
self.add_node(h, Node(is_switch = False))
self.add_edge(s, h, Edge())
for s in switches:
if s != k:
self.add_edge(s, s + 1, Edge())
if enable_all:
self.enable_all()
lastSwitch = None
for i in irange(1, k):
host = self.add_host('h%s' % i)
switch = self.add_switch('s%s' % i)
self.add_link( host, switch)
if lastSwitch:
self.add_link( switch, lastSwitch)
lastSwitch = switch
+10 -19
View File
@@ -1,6 +1,6 @@
"Library of potentially useful topologies for Mininet"
from mininet.topo import Topo, Node
from mininet.topo import Topo
from mininet.net import Mininet
class TreeTopo( Topo ):
@@ -8,36 +8,27 @@ class TreeTopo( Topo ):
def __init__( self, depth=1, fanout=2 ):
super( TreeTopo, self ).__init__()
# Numbering: h1..N, sN+1..M
hostCount = fanout ** depth
# Numbering: h1..N, s1..M
self.hostNum = 1
self.switchNum = hostCount + 1
self.switchNum = 1
# Build topology
self.addTree( depth, fanout )
# Consider all switches and hosts 'on'
self.enable_all()
# It is OK that i is "unused" in the for loop.
# pylint: disable-msg=W0612
def addTree( self, depth, fanout ):
"""Add a subtree starting with node n.
returns: last node added"""
isSwitch = depth > 0
if isSwitch:
num = self.switchNum
node = self.add_switch( 's%s' % self.switchNum )
self.switchNum += 1
else:
num = self.hostNum
self.hostNum += 1
self.add_node( num, Node( is_switch=isSwitch ) )
if isSwitch:
for i in range( 0, fanout ):
for _ in range( fanout ):
child = self.addTree( depth - 1, fanout )
self.add_edge( num, child )
return num
self.add_link( node, child )
else:
node = self.add_host( 'h%s' % self.hostNum )
self.hostNum += 1
return node
# pylint: enable-msg=W0612
def TreeNet( depth=1, fanout=2, **kwargs ):
"Convenience function for creating tree networks."
+22 -1
View File
@@ -224,6 +224,18 @@ def ipNum( w, x, y, z ):
returns: w << 24 | x << 16 | y << 8 | z"""
return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
"""Return IP address string from ints
i: int to be added to ipbase
prefixLen: optional IP prefix length
ipBaseNum: option base IP address as int
returns IP address as string"""
# Ugly but functional
assert i < ( 1 << ( 32 - prefixLen ) )
mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
ipnum = i + ( ipBaseNum & mask )
return ipStr( ipnum )
def ipParse( ip ):
"Parse an IP address and return an unsigned int."
args = [ int( arg ) for arg in ip.split( '.' ) ]
@@ -275,9 +287,13 @@ def natural( text ):
"To sort sanely/alphabetically: sorted( l, key=natural )"
def num( s ):
"Convert text segment to int if necessary"
return int( s ) if s.isdigit() else text
return int( s ) if s.isdigit() else s
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
def naturalSeq( t ):
"Natural sort key function for sequences"
return [ natural( x ) for x in t ]
def numCores():
"Returns number of CPU cores based on /proc/cpuinfo"
if hasattr( numCores, 'ncores' ):
@@ -288,6 +304,11 @@ def numCores():
return 0
return numCores.ncores
def irange(start, end):
"""Inclusive range from start to end (vs. Python insanity.)
irange(1,5) -> 1, 2, 3, 4, 5"""
return range( start, end + 1 )
def custom( cls, **params ):
"Returns customized constructor for class cls."
def customized( *args, **kwargs):